@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,627 @@
1
+ import { OpenApi } from "@typia/interface";
2
+ import {
3
+ AutoBeAutoViewScaffoldEvent,
4
+ IAutoBeCompiler,
5
+ } from "../typings";
6
+ import { v7 } from "uuid";
7
+
8
+ import { AutoViewFrontendTemplate } from "../constants/AutoViewFrontendTemplate";
9
+ import { IAutoViewAgentContext } from "../context/IAutoViewAgentContext";
10
+ import { IAutoViewProductPlan } from "./structures/IAutoViewProductPlan";
11
+ import { IAutoViewSdkMap } from "./structures/IAutoViewSdkMap";
12
+
13
+ /**
14
+ * Phase 3 of the AutoView agent — Scaffold.
15
+ *
16
+ * Deterministic. No LLM calls. Produces the file layout of a runnable Next.js +
17
+ * shadcn/ui project shaped for the in-house Sandbox platform:
18
+ *
19
+ * - Copies the bundled frontend pack (`internals/template/frontend/`) verbatim —
20
+ * Dockerfile, configs, shadcn/ui primitives, base app shell, health route.
21
+ * - Adds the AutoBE-generated SDK under `src/api/**`.
22
+ * - Adds an SDK adapter and a mock `IConnection` so generated pages can import a
23
+ * single helper instead of touching the SDK directly.
24
+ * - Materializes one placeholder TSX page per screen in
25
+ * {@link IAutoViewProductPlan} so the project compiles without Phase 4 having
26
+ * run yet; Phase 4 (Render) overwrites these in place.
27
+ * - Drops the Phase 1 and Phase 2 markdown into `wiki/` so the operator can audit
28
+ * the agent's intent alongside the code.
29
+ */
30
+ export async function orchestrateAutoViewScaffold(
31
+ ctx: IAutoViewAgentContext,
32
+ props: {
33
+ document: OpenApi.IDocument;
34
+ sdkMap: IAutoViewSdkMap;
35
+ sdkMapMarkdown: string;
36
+ productPlan: IAutoViewProductPlan;
37
+ productPlanMarkdown: string;
38
+ step: number;
39
+ /**
40
+ * Backend hint from the orchestrator. When the source carries a real server
41
+ * URL (shopping fixture), scaffold a live-mode connection so the dev server
42
+ * renders real data. When `host` is `null` (AutoBE interface phase), fall
43
+ * back to simulate-only.
44
+ */
45
+ backend: { host: string | null };
46
+ },
47
+ ): Promise<Record<string, string>> {
48
+ const files: Record<string, string> = {};
49
+
50
+ // 1. Bundled frontend pack — Dockerfile, configs, shadcn/ui base, app
51
+ // shell, health route, CLAUDE.md / PROMPT.md / SANDBOX.md.
52
+ for (const [key, value] of Object.entries(AutoViewFrontendTemplate)) {
53
+ files[key] = value;
54
+ }
55
+
56
+ // 2. SDK under `src/api/**` from the AutoBE interface compiler.
57
+ const compiler: IAutoBeCompiler = await ctx.compiler();
58
+ const written: Record<string, string> = await compiler.interface.write(
59
+ props.document,
60
+ [],
61
+ );
62
+ for (const [key, value] of Object.entries(written)) {
63
+ if (key.startsWith("src/api/")) {
64
+ files[key] = value;
65
+ }
66
+ }
67
+
68
+ // 3. SDK adapter + mock connection. Generated pages import from
69
+ // `@/lib/api` and `@/lib/connection` instead of touching the raw SDK,
70
+ // so the Render phase has one stable shape to talk to.
71
+ files["src/lib/connection.ts"] = renderConnectionTs(props.backend);
72
+ files["src/lib/api.ts"] = SDK_ADAPTER_TS;
73
+
74
+ // BootstrapGate + root layout override. The gate runs `bootstrapAuth()`
75
+ // once on mount before any page-level SDK call fires, so individual
76
+ // pages do not need to know about auth. When the bootstrap is a
77
+ // no-op (simulate / no-backend variant) children render immediately.
78
+ // The template ships a layout.tsx that does not wire the gate;
79
+ // overriding it here keeps the gate change in one place.
80
+ files["src/components/BootstrapGate.tsx"] = BOOTSTRAP_GATE_TSX;
81
+ files["app/layout.tsx"] = ROOT_LAYOUT_TSX;
82
+
83
+ // Navigation config consumed by the AppShell sidebar — generated from the
84
+ // top-level resources so the chrome reflects this swagger's domain.
85
+ files["src/lib/nav.ts"] = renderNavTs(props.document, props.productPlan);
86
+
87
+ // 4. Placeholder TSX per planned screen. Keeps `tsc --noEmit` happy
88
+ // and Next.js routable even before Phase 4 has filled the pages in.
89
+ // Phase 4 (Render) overwrites these by path.
90
+ for (const screen of props.productPlan.screens) {
91
+ const filePath = screenPathToFile(screen.path);
92
+ if (filePath === null) continue;
93
+ files[filePath] = renderPlaceholderPage(screen);
94
+ }
95
+
96
+ // 5. Wiki — Phase 1 + Phase 2 evidence next to the code.
97
+ files["wiki/sdk-map.md"] = props.sdkMapMarkdown;
98
+ files["wiki/product-plan.md"] = props.productPlanMarkdown;
99
+
100
+ ctx.dispatch({
101
+ type: "autoViewScaffold",
102
+ id: v7(),
103
+ created_at: new Date().toISOString(),
104
+ step: props.step,
105
+ files: Object.keys(files).length,
106
+ } satisfies AutoBeAutoViewScaffoldEvent);
107
+
108
+ return files;
109
+ }
110
+
111
+ /* -------------------------------------------------------------------------- */
112
+ /* path → file mapping */
113
+ /* -------------------------------------------------------------------------- */
114
+
115
+ /**
116
+ * Map a Next.js route path (`/catalog`, `/orders/[id]`) to the corresponding
117
+ * `app/.../page.tsx` location.
118
+ *
119
+ * Returns `null` for the bare `/` route — the bundled frontend pack already
120
+ * ships `app/page.tsx`, and Phase 4 overwrites it directly.
121
+ */
122
+ function screenPathToFile(routePath: string): string | null {
123
+ const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
124
+ if (trimmed.length === 0) return "app/page.tsx";
125
+ return `app/${trimmed}/page.tsx`;
126
+ }
127
+
128
+ function renderPlaceholderPage(screen: IAutoViewProductPlan.IScreen): string {
129
+ const title = jsonString(screen.title);
130
+ const purpose = jsonString(screen.purpose);
131
+ const pattern = jsonString(screen.uiPattern);
132
+ const actor = jsonString(screen.actor);
133
+ const endpointsArrayLit = JSON.stringify(screen.endpoints);
134
+ return `// Placeholder generated by AutoView Phase 3 (Scaffold).
135
+ // Phase 4 (Render) replaces this file with a real Next.js page composed of the
136
+ // SDK endpoints listed in the product plan. Until then the page renders a
137
+ // short summary so \`next build\` succeeds and the operator can navigate.
138
+
139
+ export default function Page() {
140
+ const endpoints: readonly string[] = ${endpointsArrayLit};
141
+ return (
142
+ <main className="container mx-auto py-10">
143
+ <p className="text-xs uppercase tracking-wide text-muted-foreground">
144
+ AutoView placeholder · {${pattern}}
145
+ </p>
146
+ <h1 className="mt-2 text-3xl font-bold tracking-tight">{${title}}</h1>
147
+ <p className="mt-3 text-muted-foreground">{${purpose}}</p>
148
+ <p className="mt-2 text-xs text-muted-foreground">
149
+ Actor: <span className="font-mono">{${actor}}</span>
150
+ </p>
151
+ {endpoints.length > 0 ? (
152
+ <div className="mt-4 rounded-md border bg-muted/30 p-3">
153
+ <p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
154
+ Planned SDK endpoints
155
+ </p>
156
+ <ul className="mt-2 space-y-1 text-xs font-mono">
157
+ {endpoints.map((accessor) => (
158
+ <li key={accessor}>{accessor}</li>
159
+ ))}
160
+ </ul>
161
+ </div>
162
+ ) : null}
163
+ </main>
164
+ );
165
+ }
166
+ `;
167
+ }
168
+
169
+ function jsonString(text: string): string {
170
+ return JSON.stringify(text);
171
+ }
172
+
173
+ /* -------------------------------------------------------------------------- */
174
+ /* static helpers */
175
+ /* -------------------------------------------------------------------------- */
176
+
177
+ /**
178
+ * Render the connection module.
179
+ *
180
+ * Two shapes:
181
+ *
182
+ * - When `backend.host` is non-null (shopping fixture or any SDK whose swagger
183
+ * declares `servers[]`), default to that host + `simulate: false` so pages
184
+ * render real product data on first boot. The operator can flip back to mocks
185
+ * via `NEXT_PUBLIC_API_SIMULATE=true`.
186
+ * - When `backend.host` is null (AutoBE interface phase — no live backend exists
187
+ * yet), default to `simulate: true` so the dev server boots without a real
188
+ * server behind it.
189
+ */
190
+ function renderConnectionTs(backend: { host: string | null }): string {
191
+ if (backend.host !== null) {
192
+ return `import type { IConnection } from "@nestia/fetcher";
193
+
194
+ /**
195
+ * Connection used by every generated page.
196
+ *
197
+ * Routed through Next.js's same-origin rewrite so the browser does not
198
+ * have to deal with the upstream backend's CORS policy — the samchon
199
+ * shopping backend, for instance, only advertises \`GET,HEAD,POST\` in
200
+ * its \`Access-Control-Allow-Methods\` response, which blocks every
201
+ * PATCH / PUT / DELETE preflight a typical catalog page issues. The
202
+ * rewrite lives in \`next.config.mjs\` and forwards \`/_backend/*\` to
203
+ * the real upstream (\`http://127.0.0.1:37001\` by default, see
204
+ * \`scripts/start-shopping-backend.sh\`). Override the upstream via
205
+ * \`NEXT_PUBLIC_API_HOST\` if you want to point elsewhere.
206
+ *
207
+ * Set \`NEXT_PUBLIC_API_SIMULATE=true\` to swap to typia-random mocks
208
+ * (the SDK then never hits the network).
209
+ *
210
+ * The \`Authorization\` header starts empty — \`bootstrapAuth()\` populates
211
+ * it on first call with a guest customer token issued by the shopping
212
+ * backend. \`BootstrapGate\` in the root layout invokes the bootstrap
213
+ * once on mount, before any page-level SDK call fires, so individual
214
+ * page components do not need to think about auth.
215
+ */
216
+ /* -------------------------------------------------------------------------- */
217
+ /* connection scope */
218
+ /* -------------------------------------------------------------------------- */
219
+
220
+ /**
221
+ * Which actor's token the SDK is currently authenticated as. The browser
222
+ * pathname picks the scope on every navigation — see \`BootstrapGate\`. A
223
+ * \`null\` scope means the bootstrap has not run yet.
224
+ */
225
+ export type AuthScope = null | "customer" | "citizen" | "administrator" | "seller";
226
+
227
+ let activeScope: AuthScope = null;
228
+ const scopePromises: Partial<Record<Exclude<AuthScope, null>, Promise<void>>> = {};
229
+
230
+ /**
231
+ * Seed credentials for the samchon shopping backend. The bundled
232
+ * \`scripts/start-shopping-backend.sh\` always seeds the same single account,
233
+ * so these values are constants. \`SHOPPING_SYSTEM_PASSWORD\` defaults to
234
+ * \`samchon\` in the upstream \`.env\` (see seeders).
235
+ */
236
+ const SEED_EMAIL = "robot@nestia.io";
237
+ const SEED_PASSWORD = "samchon";
238
+ const SEED_NICKNAME = "Robot";
239
+ const SEED_CITIZEN_MOBILE = "01012345678";
240
+ const SEED_CITIZEN_NAME = "Robot";
241
+ const CHANNEL_CODE = "samchon";
242
+
243
+ export const connection: IConnection = {
244
+ host:
245
+ typeof window !== "undefined"
246
+ ? \`\${window.location.origin}/_backend\`
247
+ : (process?.env?.NEXT_PUBLIC_API_HOST ?? ${JSON.stringify(backend.host)}),
248
+ headers: {
249
+ Authorization: process?.env?.NEXT_PUBLIC_API_TOKEN ?? "",
250
+ },
251
+ simulate:
252
+ typeof process !== "undefined" &&
253
+ process.env.NEXT_PUBLIC_API_SIMULATE === "true",
254
+ };
255
+
256
+ /**
257
+ * Choose which actor's session the current page needs based on the URL.
258
+ * Defaults to plain customer (read-only catalog browsing). Admin and seller
259
+ * surfaces sign in with the seed account; coupons / orders / wallet /
260
+ * checkout / account need a citizen (the customer plus mobile/name).
261
+ */
262
+ function scopeForPathname(pathname: string | null | undefined): Exclude<AuthScope, null> {
263
+ if (typeof pathname !== "string") return "customer";
264
+ if (pathname.startsWith("/admin")) return "administrator";
265
+ if (pathname.startsWith("/seller")) return "seller";
266
+ if (
267
+ pathname.startsWith("/cart") ||
268
+ pathname.startsWith("/orders") ||
269
+ pathname.startsWith("/checkout") ||
270
+ pathname.startsWith("/wallet") ||
271
+ pathname.startsWith("/coupons") ||
272
+ pathname.startsWith("/account")
273
+ ) {
274
+ return "citizen";
275
+ }
276
+ return "customer";
277
+ }
278
+
279
+ async function requestJson<T>(
280
+ method: "POST" | "PUT",
281
+ path: string,
282
+ body: unknown,
283
+ ): Promise<T> {
284
+ const res = await fetch(\`/_backend\${path}\`, {
285
+ method,
286
+ headers: { "Content-Type": "application/json", ...auth() },
287
+ body: JSON.stringify(body),
288
+ });
289
+ if (!res.ok) {
290
+ const text = await res.text().catch(() => "");
291
+ throw new Error(\`\${method} \${path} → \${res.status}: \${text.slice(0, 200)}\`);
292
+ }
293
+ return (await res.json()) as T;
294
+ }
295
+
296
+ function auth(): Record<string, string> {
297
+ const a = connection.headers?.Authorization;
298
+ return typeof a === "string" && a.length > 0 ? { Authorization: a } : {};
299
+ }
300
+
301
+ function applyToken(payload: unknown): void {
302
+ const raw = payload as {
303
+ setHeaders?: { Authorization?: unknown } | null;
304
+ token?: { access?: unknown } | null;
305
+ } | null;
306
+ const fromSetHeaders =
307
+ typeof raw?.setHeaders?.Authorization === "string"
308
+ ? (raw.setHeaders.Authorization as string)
309
+ : null;
310
+ const fromTokenAccess =
311
+ typeof raw?.token?.access === "string"
312
+ ? \`Bearer \${raw.token.access as string}\`
313
+ : null;
314
+ const token = fromSetHeaders ?? fromTokenAccess;
315
+ if (token === null) {
316
+ throw new Error("Auth bootstrap returned 2xx but no token in the body.");
317
+ }
318
+ if (connection.headers !== undefined) {
319
+ connection.headers.Authorization = token;
320
+ }
321
+ }
322
+
323
+ async function ensureCustomer(): Promise<void> {
324
+ // The customer \`POST /authenticate\` is the only call that issues a
325
+ // new JWT — every subsequent activate/login attaches role grants to
326
+ // the same customer server-side and returns the updated customer
327
+ // object with NO \`setHeaders\` / \`token\` field. Reusing the bearer
328
+ // header from this call works for citizen / member / admin / seller
329
+ // requests after the corresponding activate or login has succeeded.
330
+ await requestJson("POST", "/shoppings/customers/authenticate", {
331
+ channel_code: CHANNEL_CODE,
332
+ external_user: null,
333
+ href: typeof window !== "undefined" ? window.location.href : "http://localhost:3000",
334
+ referrer: typeof document !== "undefined" ? document.referrer || null : null,
335
+ }).then(applyToken);
336
+ }
337
+
338
+ async function ensureCitizen(): Promise<void> {
339
+ await ensureCustomer();
340
+ // \`activate\` upgrades the current customer to a citizen by attaching
341
+ // mobile/name. Returns the updated customer (no \`setHeaders\` / no
342
+ // \`token\` field) so the customer JWT from \`ensureCustomer\` stays the
343
+ // auth header for citizen-scoped routes. Idempotent for the seeded
344
+ // account; backend rejects re-activation with 409/422 which we
345
+ // swallow as success.
346
+ try {
347
+ await requestJson("POST", "/shoppings/customers/authenticate/activate", {
348
+ mobile: SEED_CITIZEN_MOBILE,
349
+ name: SEED_CITIZEN_NAME,
350
+ });
351
+ } catch (e: unknown) {
352
+ if (e instanceof Error && /already|conflict|409|422/i.test(e.message)) {
353
+ return;
354
+ }
355
+ throw e;
356
+ }
357
+ }
358
+
359
+ async function ensureMemberLogin(): Promise<void> {
360
+ await ensureCustomer();
361
+ // \`login\` is registered as PUT on the samchon/shopping backend
362
+ // (\`@TypedRoute.Put("login")\`), not POST. The response has no
363
+ // \`setHeaders\` / no \`token\` field — the existing customer JWT now
364
+ // carries member privileges server-side, so no header rewrite is
365
+ // needed.
366
+ await requestJson("PUT", "/shoppings/customers/authenticate/login", {
367
+ email: SEED_EMAIL,
368
+ password: SEED_PASSWORD,
369
+ });
370
+ }
371
+
372
+ async function ensureAdministrator(): Promise<void> {
373
+ await ensureMemberLogin();
374
+ await requestJson("PUT", "/shoppings/admins/authenticate/login", {
375
+ email: SEED_EMAIL,
376
+ password: SEED_PASSWORD,
377
+ });
378
+ }
379
+
380
+ async function ensureSeller(): Promise<void> {
381
+ await ensureMemberLogin();
382
+ await requestJson("PUT", "/shoppings/sellers/authenticate/login", {
383
+ email: SEED_EMAIL,
384
+ password: SEED_PASSWORD,
385
+ });
386
+ }
387
+
388
+ /**
389
+ * Bootstrap (or rotate) the connection's auth token to the scope demanded
390
+ * by the current pathname.
391
+ *
392
+ * Memoized per scope: the first call per scope drives the fetch chain,
393
+ * concurrent callers reuse the same promise, and successful runs are
394
+ * cached so navigations within the same scope are instant. Failures clear
395
+ * the cache so the next attempt retries cleanly.
396
+ *
397
+ * When \`simulate: true\` is on, this is a no-op because the SDK never
398
+ * touches the network in mock mode.
399
+ */
400
+ export function bootstrapAuth(pathname?: string): Promise<void> {
401
+ if (connection.simulate === true) {
402
+ activeScope = "customer";
403
+ return Promise.resolve();
404
+ }
405
+ const scope = scopeForPathname(pathname);
406
+ if (scope === activeScope && scopePromises[scope] !== undefined) {
407
+ return scopePromises[scope]!;
408
+ }
409
+ const promise = (async () => {
410
+ if (scope === "administrator") await ensureAdministrator();
411
+ else if (scope === "seller") await ensureSeller();
412
+ else if (scope === "citizen") await ensureCitizen();
413
+ else await ensureCustomer();
414
+ })()
415
+ .then(() => {
416
+ activeScope = scope;
417
+ })
418
+ .catch((err: unknown) => {
419
+ scopePromises[scope] = undefined;
420
+ throw err;
421
+ });
422
+ scopePromises[scope] = promise;
423
+ return promise;
424
+ }
425
+ `;
426
+ }
427
+ return `import type { IConnection } from "@nestia/fetcher";
428
+
429
+ /**
430
+ * Mock connection used by every generated page.
431
+ *
432
+ * Defaults to \`simulate: true\` so the project boots usefully without a
433
+ * live backend — the SDK returns typia-generated mock responses for every
434
+ * endpoint. Set \`NEXT_PUBLIC_API_SIMULATE=false\` (and point
435
+ * \`NEXT_PUBLIC_API_HOST\` at the real server) to switch to live mode.
436
+ */
437
+ export const connection: IConnection = {
438
+ host:
439
+ typeof process !== "undefined"
440
+ ? process.env.NEXT_PUBLIC_API_HOST ?? "http://127.0.0.1:37001"
441
+ : "http://127.0.0.1:37001",
442
+ headers: {
443
+ Authorization: process?.env?.NEXT_PUBLIC_API_TOKEN ?? "Bearer preview",
444
+ },
445
+ simulate:
446
+ typeof process === "undefined" ||
447
+ process.env.NEXT_PUBLIC_API_SIMULATE !== "false",
448
+ };
449
+
450
+ /**
451
+ * No-op auth bootstrap for the interface (no-backend) variant. Accepts
452
+ * (and ignores) the pathname so the root layout can call
453
+ * \`bootstrapAuth(pathname)\` unconditionally, no matter which AutoView
454
+ * source generated the project.
455
+ */
456
+ export function bootstrapAuth(_pathname?: string): Promise<void> {
457
+ return Promise.resolve();
458
+ }
459
+ `;
460
+ }
461
+
462
+ const BOOTSTRAP_GATE_TSX = `"use client";
463
+
464
+ import { usePathname } from "next/navigation";
465
+ import { useEffect } from "react";
466
+
467
+ import { bootstrapAuth } from "@/src/lib/connection";
468
+
469
+ /**
470
+ * Best-effort auth bootstrap. Attempts to issue the backend's token for the
471
+ * current actor scope on navigation, but NEVER blocks rendering.
472
+ *
473
+ * Auth must not gate the whole app: public screens (catalogs, lists) work
474
+ * without a token, and protected screens surface their own 401 through their
475
+ * normal load/error state. An auth-required backend (shopping / ERP) or a
476
+ * backend that is simply down used to take the ENTIRE app down behind a
477
+ * "Backend not ready" wall — even the public screens. It no longer does:
478
+ * the bootstrap runs in the background and children render immediately.
479
+ */
480
+ export default function BootstrapGate({
481
+ children,
482
+ }: {
483
+ children: React.ReactNode;
484
+ }) {
485
+ const pathname = usePathname();
486
+
487
+ useEffect(() => {
488
+ bootstrapAuth(pathname).catch(() => {
489
+ // Bootstrap failed (no seeded account, auth-required backend, or backend
490
+ // down). Render anyway — each page handles its own loading / error state.
491
+ });
492
+ }, [pathname]);
493
+
494
+ return <>{children}</>;
495
+ }
496
+ `;
497
+
498
+ const ROOT_LAYOUT_TSX = `import type { Metadata } from "next";
499
+ import { Archivo } from "next/font/google";
500
+ import "./globals.css";
501
+
502
+ import { AppShell } from "@/components/AppShell";
503
+ import BootstrapGate from "@/src/components/BootstrapGate";
504
+ import { APP_NAME } from "@/src/lib/nav";
505
+
506
+ // Industrial grotesque carrying the whole UI. Wires the \`--font-sans\` variable
507
+ // the theme reads; the gate override must keep it or headings fall back to the
508
+ // browser's default serif.
509
+ const archivo = Archivo({
510
+ subsets: ["latin"],
511
+ variable: "--font-sans",
512
+ display: "swap",
513
+ });
514
+
515
+ export const metadata: Metadata = {
516
+ title: APP_NAME,
517
+ description: "Generated by AutoView from the API's OpenAPI document.",
518
+ };
519
+
520
+ export default function RootLayout({
521
+ children,
522
+ }: {
523
+ children: React.ReactNode;
524
+ }) {
525
+ return (
526
+ <html lang="en" className={\`dark \${archivo.variable}\`} suppressHydrationWarning>
527
+ <body className="min-h-screen bg-background font-sans antialiased">
528
+ <BootstrapGate>
529
+ <AppShell>{children}</AppShell>
530
+ </BootstrapGate>
531
+ </body>
532
+ </html>
533
+ );
534
+ }
535
+ `;
536
+
537
+ /**
538
+ * Generate `src/lib/nav.ts` — the sidebar navigation config the AppShell reads.
539
+ * Top-level resources (chain depth 1) become nav items, grouped into data
540
+ * "Resources" (list tables) and "System" (singleton reads). The app name comes
541
+ * from the OpenAPI document title.
542
+ */
543
+ export function renderNavTs(
544
+ document: OpenApi.IDocument,
545
+ plan: IAutoViewProductPlan,
546
+ ): string {
547
+ const appName = (document.info?.title ?? "API").trim() || "API";
548
+ const navScreens = plan.screens.filter(
549
+ (s) =>
550
+ (s.depth === undefined || s.depth === 1) &&
551
+ !s.path.includes("[") &&
552
+ (s.uiPattern === "table" ||
553
+ s.uiPattern === "catalog" ||
554
+ s.uiPattern === "detail"),
555
+ );
556
+ // Singleton reads go under "System"; browsable resources go under "Resources",
557
+ // but on a large swagger that one section is 36 flat items. Group them by the
558
+ // resource name's leading token so related resources collapse into a section
559
+ // (shield_information_barrier* → "Shield", legal_hold_* → "Legal"). A token
560
+ // shared by ≥2 resources becomes its own section; lone resources stay under
561
+ // the generic "Resources" heading. Purely cosmetic — no screen is removed.
562
+ const groupKey = new Map<string, string>(); // href → leading token (singularized)
563
+ const tokenCount = new Map<string, number>();
564
+ for (const s of navScreens) {
565
+ if (s.uiPattern === "detail") continue;
566
+ const resource = s.path.replace(/^\//, "").split("/")[0] ?? "";
567
+ const token = singularizeToken(resource.split("_")[0] ?? resource);
568
+ groupKey.set(s.path, token);
569
+ tokenCount.set(token, (tokenCount.get(token) ?? 0) + 1);
570
+ }
571
+ const items = navScreens.map((s) => {
572
+ if (s.uiPattern === "detail") {
573
+ return { href: s.path, title: s.title, group: "System" };
574
+ }
575
+ const token = groupKey.get(s.path) ?? "";
576
+ const group =
577
+ (tokenCount.get(token) ?? 0) >= 2 ? titleCaseToken(token) : "Resources";
578
+ return { href: s.path, title: s.title, group };
579
+ });
580
+ // "Resources" first, named prefix groups alphabetically, "System" last.
581
+ const rank = (g: string): number =>
582
+ g === "Resources" ? 0 : g === "System" ? 2 : 1;
583
+ items.sort(
584
+ (a, b) =>
585
+ rank(a.group) - rank(b.group) ||
586
+ a.group.localeCompare(b.group) ||
587
+ a.title.localeCompare(b.title),
588
+ );
589
+ return [
590
+ `// Generated by AutoView — sidebar navigation derived from the top-level`,
591
+ `// resources of the OpenAPI document.`,
592
+ `export const APP_NAME = ${JSON.stringify(appName)};`,
593
+ ``,
594
+ `export interface NavItem {`,
595
+ ` href: string;`,
596
+ ` title: string;`,
597
+ ` group: string;`,
598
+ `}`,
599
+ ``,
600
+ `export const NAV: NavItem[] = ${JSON.stringify(items, null, 2)};`,
601
+ ``,
602
+ ].join("\n");
603
+ }
604
+
605
+ /** Crude singularization of a leading nav token (`folders` → `folder`), used
606
+ * only to bucket related resources; `status`-style 2-letter stems are left
607
+ * alone to avoid over-trimming. */
608
+ function singularizeToken(token: string): string {
609
+ return token.length > 3 && token.endsWith("s") ? token.slice(0, -1) : token;
610
+ }
611
+
612
+ /** Title-case a single token for a nav section heading (`shield` → `Shield`). */
613
+ function titleCaseToken(token: string): string {
614
+ return token.length === 0
615
+ ? token
616
+ : token.charAt(0).toUpperCase() + token.slice(1);
617
+ }
618
+
619
+ const SDK_ADAPTER_TS = `// AutoView SDK adapter. Pages import \`api\` from this module instead of
620
+ // touching \`@/src/api/\` directly so the SDK surface can be replaced
621
+ // without rewriting every page.
622
+
623
+ import api from "@/src/api";
624
+
625
+ export default api;
626
+ export * from "@/src/api";
627
+ `;