@askrjs/cli 0.0.3 → 0.0.4

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 (287) hide show
  1. package/README.md +37 -0
  2. package/dist/add.d.ts +16 -0
  3. package/dist/add.js +33 -29
  4. package/dist/cli.d.ts +5 -0
  5. package/dist/cli.js +0 -2
  6. package/dist/create.d.ts +5 -0
  7. package/dist/create.js +104 -26
  8. package/dist/directory-swap-DWoHtx7C.js +35 -0
  9. package/dist/discovery-Difb7Y4G.js +0 -2
  10. package/dist/generate.d.ts +5 -0
  11. package/dist/generate.js +77 -12
  12. package/dist/is-direct-execution-Cdlr-ZUl.js +0 -2
  13. package/dist/openapi.d.ts +25 -0
  14. package/dist/openapi.js +44 -12
  15. package/dist/planner-VAj7qlxr.js +0 -2
  16. package/dist/range-YUs9eimn.js +0 -2
  17. package/dist/registry-CyrRHRG5.js +0 -2
  18. package/dist/skills/askr-ssr-ssg/SKILL.md +3 -1
  19. package/dist/{skills-Bh0mEhIx.js → skills-B7CbWur9.js} +89 -26
  20. package/dist/skills.d.ts +18 -0
  21. package/dist/skills.js +1 -1
  22. package/dist/specification-DXnDOC-0.js +0 -2
  23. package/dist/ssg-config.d.ts +41 -0
  24. package/dist/ssg-config.js +1 -0
  25. package/dist/ssg.d.ts +61 -0
  26. package/dist/ssg.js +360 -24
  27. package/dist/templates/full-stack/package.json +1 -1
  28. package/dist/templates/full-stack/src/server/action-registry.ts +0 -1
  29. package/dist/templates/full-stack/tsconfig.json +1 -1
  30. package/dist/templates/spa/package.json +2 -3
  31. package/dist/templates/spa/src/pages/app/_routes.tsx +2 -0
  32. package/dist/templates/spa/src/pages/public/_routes.tsx +2 -0
  33. package/dist/templates/spa/tsconfig.json +2 -1
  34. package/dist/templates/spa/vitest.config.ts +0 -4
  35. package/dist/templates/ssg/README.md +1 -1
  36. package/dist/templates/ssg/package.json +1 -1
  37. package/dist/templates/ssg/ssg.config.ts +7 -0
  38. package/dist/templates/ssg/tests/ssg-config.test.ts +2 -0
  39. package/dist/templates/ssg/tsconfig.json +2 -1
  40. package/dist/templates/ssg/vitest.config.ts +0 -4
  41. package/dist/templates/ssr/package.json +2 -2
  42. package/dist/templates/startkit/package.json +2 -2
  43. package/dist/templates/startkit/src/lib/mock-data.ts +17 -39
  44. package/dist/templates/startkit/tsconfig.json +2 -1
  45. package/dist/update.d.ts +41 -0
  46. package/dist/update.js +0 -2
  47. package/dist/writer-D8qe_7ud.js +0 -2
  48. package/package.json +13 -8
  49. package/dist/add.js.map +0 -1
  50. package/dist/cli.js.map +0 -1
  51. package/dist/create.js.map +0 -1
  52. package/dist/discovery-Difb7Y4G.js.map +0 -1
  53. package/dist/generate.js.map +0 -1
  54. package/dist/is-direct-execution-Cdlr-ZUl.js.map +0 -1
  55. package/dist/openapi.js.map +0 -1
  56. package/dist/planner-VAj7qlxr.js.map +0 -1
  57. package/dist/range-YUs9eimn.js.map +0 -1
  58. package/dist/registry-CyrRHRG5.js.map +0 -1
  59. package/dist/skills-Bh0mEhIx.js.map +0 -1
  60. package/dist/specification-DXnDOC-0.js.map +0 -1
  61. package/dist/ssg.js.map +0 -1
  62. package/dist/templates/full-stack/src/vite-server.d.ts +0 -5
  63. package/dist/templates/spa/src/styles.d.ts +0 -1
  64. package/dist/templates/ssg/src/vite-env.d.ts +0 -5
  65. package/dist/templates/ssr/src/vite-server.d.ts +0 -7
  66. package/dist/templates/startkit/src/vite-env.d.ts +0 -5
  67. package/dist/update.js.map +0 -1
  68. package/dist/writer-D8qe_7ud.js.map +0 -1
  69. package/skills/askr-accessibility/SKILL.md +0 -68
  70. package/skills/askr-accessibility/agents/openai.yaml +0 -4
  71. package/skills/askr-agent-execution/SKILL.md +0 -85
  72. package/skills/askr-agent-execution/agents/openai.yaml +0 -4
  73. package/skills/askr-agent-workflows/SKILL.md +0 -57
  74. package/skills/askr-agent-workflows/agents/openai.yaml +0 -4
  75. package/skills/askr-api-integration/SKILL.md +0 -79
  76. package/skills/askr-api-integration/agents/openai.yaml +0 -4
  77. package/skills/askr-app-builder/SKILL.md +0 -99
  78. package/skills/askr-app-builder/agents/openai.yaml +0 -4
  79. package/skills/askr-auth-access/SKILL.md +0 -74
  80. package/skills/askr-auth-access/agents/openai.yaml +0 -4
  81. package/skills/askr-cli-vite/SKILL.md +0 -76
  82. package/skills/askr-cli-vite/agents/openai.yaml +0 -4
  83. package/skills/askr-dashboard-charts/SKILL.md +0 -99
  84. package/skills/askr-dashboard-charts/agents/openai.yaml +0 -4
  85. package/skills/askr-design-system/SKILL.md +0 -86
  86. package/skills/askr-design-system/agents/openai.yaml +0 -4
  87. package/skills/askr-env-config/SKILL.md +0 -74
  88. package/skills/askr-env-config/agents/openai.yaml +0 -4
  89. package/skills/askr-error-loading-empty/SKILL.md +0 -89
  90. package/skills/askr-error-loading-empty/agents/openai.yaml +0 -4
  91. package/skills/askr-file-upload-artifacts/SKILL.md +0 -77
  92. package/skills/askr-file-upload-artifacts/agents/openai.yaml +0 -4
  93. package/skills/askr-forms-tables-crud/SKILL.md +0 -98
  94. package/skills/askr-forms-tables-crud/agents/openai.yaml +0 -4
  95. package/skills/askr-mental-model/SKILL.md +0 -109
  96. package/skills/askr-mental-model/agents/openai.yaml +0 -4
  97. package/skills/askr-migration-react/SKILL.md +0 -81
  98. package/skills/askr-migration-react/agents/openai.yaml +0 -4
  99. package/skills/askr-observability-debugging/SKILL.md +0 -83
  100. package/skills/askr-observability-debugging/agents/openai.yaml +0 -4
  101. package/skills/askr-project-structure/SKILL.md +0 -79
  102. package/skills/askr-project-structure/agents/openai.yaml +0 -4
  103. package/skills/askr-query-mutation/SKILL.md +0 -115
  104. package/skills/askr-query-mutation/agents/openai.yaml +0 -4
  105. package/skills/askr-realtime-streaming/SKILL.md +0 -76
  106. package/skills/askr-realtime-streaming/agents/openai.yaml +0 -4
  107. package/skills/askr-resources-data/SKILL.md +0 -100
  108. package/skills/askr-resources-data/agents/openai.yaml +0 -4
  109. package/skills/askr-routing-layouts/SKILL.md +0 -107
  110. package/skills/askr-routing-layouts/agents/openai.yaml +0 -4
  111. package/skills/askr-runtime-reactivity/SKILL.md +0 -92
  112. package/skills/askr-runtime-reactivity/agents/openai.yaml +0 -4
  113. package/skills/askr-ssr-ssg/SKILL.md +0 -76
  114. package/skills/askr-ssr-ssg/agents/openai.yaml +0 -4
  115. package/skills/askr-testing-determinism/SKILL.md +0 -75
  116. package/skills/askr-testing-determinism/agents/openai.yaml +0 -4
  117. package/skills/askr-theming/SKILL.md +0 -86
  118. package/skills/askr-theming/agents/openai.yaml +0 -4
  119. package/skills/askr-ui-composition/SKILL.md +0 -92
  120. package/skills/askr-ui-composition/agents/openai.yaml +0 -4
  121. package/templates/full-stack/AGENTS.md +0 -11
  122. package/templates/full-stack/README.md +0 -17
  123. package/templates/full-stack/gitignore.template +0 -4
  124. package/templates/full-stack/index.html +0 -13
  125. package/templates/full-stack/package.json +0 -39
  126. package/templates/full-stack/server.ts +0 -9
  127. package/templates/full-stack/src/action-authorizations.ts +0 -10
  128. package/templates/full-stack/src/actions/create-message.ts +0 -10
  129. package/templates/full-stack/src/i18n.ts +0 -8
  130. package/templates/full-stack/src/main.tsx +0 -11
  131. package/templates/full-stack/src/pages/home.tsx +0 -25
  132. package/templates/full-stack/src/pages/layout.tsx +0 -43
  133. package/templates/full-stack/src/pages/not-found.tsx +0 -7
  134. package/templates/full-stack/src/routes.tsx +0 -19
  135. package/templates/full-stack/src/schemas.ts +0 -10
  136. package/templates/full-stack/src/server/action-registry.ts +0 -8
  137. package/templates/full-stack/src/server/actions/create-message.ts +0 -14
  138. package/templates/full-stack/src/server/app.ts +0 -76
  139. package/templates/full-stack/src/server/dependencies.ts +0 -55
  140. package/templates/full-stack/src/server/entry-server.ts +0 -8
  141. package/templates/full-stack/src/telemetry.ts +0 -3
  142. package/templates/full-stack/src/vite-server.d.ts +0 -5
  143. package/templates/full-stack/tests/actions/create-message.test.ts +0 -12
  144. package/templates/full-stack/tsconfig.json +0 -15
  145. package/templates/full-stack/vite.config.ts +0 -10
  146. package/templates/spa/AGENTS.md +0 -60
  147. package/templates/spa/README.md +0 -96
  148. package/templates/spa/gitignore.template +0 -16
  149. package/templates/spa/index.html +0 -13
  150. package/templates/spa/package.json +0 -35
  151. package/templates/spa/src/adapters/operations-client.ts +0 -95
  152. package/templates/spa/src/components/shared/metric-card.tsx +0 -27
  153. package/templates/spa/src/components/shared/status-badge.tsx +0 -24
  154. package/templates/spa/src/features/operations/operations.query.ts +0 -9
  155. package/templates/spa/src/main.tsx +0 -10
  156. package/templates/spa/src/pages/_layout.tsx +0 -9
  157. package/templates/spa/src/pages/_routes.tsx +0 -27
  158. package/templates/spa/src/pages/app/_layout.tsx +0 -94
  159. package/templates/spa/src/pages/app/_routes.tsx +0 -10
  160. package/templates/spa/src/pages/app/admin-home.tsx +0 -170
  161. package/templates/spa/src/pages/app/agent-runs.tsx +0 -93
  162. package/templates/spa/src/pages/app/settings.tsx +0 -91
  163. package/templates/spa/src/pages/auth/_layout.tsx +0 -38
  164. package/templates/spa/src/pages/auth/_routes.tsx +0 -6
  165. package/templates/spa/src/pages/auth/login.tsx +0 -63
  166. package/templates/spa/src/pages/not-found.tsx +0 -22
  167. package/templates/spa/src/pages/public/_layout.tsx +0 -47
  168. package/templates/spa/src/pages/public/_routes.tsx +0 -6
  169. package/templates/spa/src/pages/public/home.tsx +0 -143
  170. package/templates/spa/src/shared/format.ts +0 -12
  171. package/templates/spa/src/shared/navigation.ts +0 -15
  172. package/templates/spa/src/styles/components.css +0 -49
  173. package/templates/spa/src/styles/layout.css +0 -192
  174. package/templates/spa/src/styles/reset.css +0 -44
  175. package/templates/spa/src/styles/theme.css +0 -57
  176. package/templates/spa/src/styles/tokens.css +0 -20
  177. package/templates/spa/src/styles.css +0 -7
  178. package/templates/spa/src/styles.d.ts +0 -1
  179. package/templates/spa/tests/app.test.tsx +0 -37
  180. package/templates/spa/tests/components/shared.test.tsx +0 -12
  181. package/templates/spa/tests/resources.test.ts +0 -22
  182. package/templates/spa/tsconfig.json +0 -20
  183. package/templates/spa/tsconfig.node.json +0 -10
  184. package/templates/spa/vite.config.ts +0 -24
  185. package/templates/spa/vitest.config.ts +0 -17
  186. package/templates/ssg/AGENTS.md +0 -55
  187. package/templates/ssg/README.md +0 -39
  188. package/templates/ssg/gitignore.template +0 -17
  189. package/templates/ssg/index.html +0 -13
  190. package/templates/ssg/package.json +0 -35
  191. package/templates/ssg/src/app.tsx +0 -14
  192. package/templates/ssg/src/components/badge.tsx +0 -3
  193. package/templates/ssg/src/components/counter.tsx +0 -30
  194. package/templates/ssg/src/components/site-shell.tsx +0 -111
  195. package/templates/ssg/src/jsx.d.ts +0 -23
  196. package/templates/ssg/src/main.tsx +0 -8
  197. package/templates/ssg/src/pages/about.tsx +0 -82
  198. package/templates/ssg/src/pages/content.tsx +0 -63
  199. package/templates/ssg/src/pages/example.tsx +0 -97
  200. package/templates/ssg/src/pages/home.tsx +0 -51
  201. package/templates/ssg/src/resources/user.ts +0 -15
  202. package/templates/ssg/src/routes.tsx +0 -15
  203. package/templates/ssg/src/styles.css +0 -89
  204. package/templates/ssg/src/vite-env.d.ts +0 -5
  205. package/templates/ssg/ssg.config.ts +0 -34
  206. package/templates/ssg/tests/app.test.tsx +0 -19
  207. package/templates/ssg/tests/components/counter.test.tsx +0 -18
  208. package/templates/ssg/tests/resources.test.ts +0 -11
  209. package/templates/ssg/tests/ssg-config.test.ts +0 -11
  210. package/templates/ssg/tsconfig.json +0 -20
  211. package/templates/ssg/tsconfig.node.json +0 -10
  212. package/templates/ssg/vite.config.ts +0 -24
  213. package/templates/ssg/vitest.config.ts +0 -17
  214. package/templates/ssr/AGENTS.md +0 -28
  215. package/templates/ssr/README.md +0 -38
  216. package/templates/ssr/gitignore.template +0 -16
  217. package/templates/ssr/index.html +0 -13
  218. package/templates/ssr/package.json +0 -34
  219. package/templates/ssr/server.ts +0 -10
  220. package/templates/ssr/src/app.tsx +0 -21
  221. package/templates/ssr/src/components/counter.tsx +0 -26
  222. package/templates/ssr/src/entry-server.tsx +0 -12
  223. package/templates/ssr/src/main.tsx +0 -11
  224. package/templates/ssr/src/pages/about.tsx +0 -75
  225. package/templates/ssr/src/pages/example.tsx +0 -113
  226. package/templates/ssr/src/pages/home.tsx +0 -56
  227. package/templates/ssr/src/resources/user.ts +0 -15
  228. package/templates/ssr/src/routes.tsx +0 -13
  229. package/templates/ssr/src/styles.css +0 -148
  230. package/templates/ssr/src/vite-server.d.ts +0 -7
  231. package/templates/ssr/tests/app.test.tsx +0 -24
  232. package/templates/ssr/tests/components/counter.test.tsx +0 -13
  233. package/templates/ssr/tests/resources.test.ts +0 -11
  234. package/templates/ssr/tsconfig.json +0 -22
  235. package/templates/ssr/tsconfig.node.json +0 -10
  236. package/templates/ssr/tsconfig.server.json +0 -15
  237. package/templates/ssr/vite.config.ts +0 -25
  238. package/templates/ssr/vitest.config.ts +0 -13
  239. package/templates/startkit/AGENTS.md +0 -77
  240. package/templates/startkit/README.md +0 -192
  241. package/templates/startkit/gitignore.template +0 -16
  242. package/templates/startkit/index.html +0 -13
  243. package/templates/startkit/package.json +0 -35
  244. package/templates/startkit/src/components/app-header.tsx +0 -93
  245. package/templates/startkit/src/components/app-sidebar.tsx +0 -95
  246. package/templates/startkit/src/components/data-table.tsx +0 -80
  247. package/templates/startkit/src/components/empty-state.tsx +0 -13
  248. package/templates/startkit/src/components/page-header.tsx +0 -23
  249. package/templates/startkit/src/components/stat-card.tsx +0 -24
  250. package/templates/startkit/src/features/accounts/account-filters.tsx +0 -61
  251. package/templates/startkit/src/features/accounts/account-table.tsx +0 -79
  252. package/templates/startkit/src/lib/format.ts +0 -31
  253. package/templates/startkit/src/lib/mock-data.ts +0 -441
  254. package/templates/startkit/src/lib/routes.ts +0 -116
  255. package/templates/startkit/src/main.tsx +0 -12
  256. package/templates/startkit/src/pages/_layout.tsx +0 -56
  257. package/templates/startkit/src/pages/auth/_layout.tsx +0 -7
  258. package/templates/startkit/src/pages/auth/login.tsx +0 -129
  259. package/templates/startkit/src/pages/home.tsx +0 -83
  260. package/templates/startkit/src/pages/not-found.tsx +0 -24
  261. package/templates/startkit/src/pages/workspace/_layout.tsx +0 -17
  262. package/templates/startkit/src/pages/workspace/accounts/index.tsx +0 -217
  263. package/templates/startkit/src/pages/workspace/dashboard.tsx +0 -118
  264. package/templates/startkit/src/pages/workspace/settings.tsx +0 -218
  265. package/templates/startkit/src/router.tsx +0 -7
  266. package/templates/startkit/src/routes/auth-config.ts +0 -38
  267. package/templates/startkit/src/routes/auth.ts +0 -8
  268. package/templates/startkit/src/routes/index.ts +0 -28
  269. package/templates/startkit/src/routes/public.ts +0 -8
  270. package/templates/startkit/src/routes/workspace/accounts.ts +0 -8
  271. package/templates/startkit/src/routes/workspace/index.ts +0 -14
  272. package/templates/startkit/src/styles/components.css +0 -396
  273. package/templates/startkit/src/styles/layout.css +0 -192
  274. package/templates/startkit/src/styles/reset.css +0 -38
  275. package/templates/startkit/src/styles/theme.css +0 -76
  276. package/templates/startkit/src/styles/tokens.css +0 -67
  277. package/templates/startkit/src/styles.css +0 -7
  278. package/templates/startkit/src/toast.ts +0 -55
  279. package/templates/startkit/src/utils/join-classes.ts +0 -4
  280. package/templates/startkit/src/vite-env.d.ts +0 -5
  281. package/templates/startkit/tests/app.test.tsx +0 -32
  282. package/templates/startkit/tests/preferences.test.ts +0 -43
  283. package/templates/startkit/tests/resources.test.ts +0 -82
  284. package/templates/startkit/tsconfig.json +0 -20
  285. package/templates/startkit/tsconfig.node.json +0 -10
  286. package/templates/startkit/vite.config.ts +0 -32
  287. package/templates/startkit/vitest.config.ts +0 -13
package/README.md CHANGED
@@ -50,6 +50,43 @@ unless you opt out with `--no-skills`.
50
50
  The installed command is `askr`. Subcommands are intentionally not published
51
51
  as compatibility binaries.
52
52
 
53
+ ## Static sitemap generation
54
+
55
+ `askr ssg` writes `sitemap.xml` from the concrete routes generated during the
56
+ build. Set the canonical deploy URL in the TypeScript config:
57
+
58
+ ```ts
59
+ export const staticConfig = {
60
+ registry,
61
+ outputDir: "./dist",
62
+ siteUrl: "https://example.com",
63
+ sitemap: {
64
+ defaults: { changeFrequency: "weekly" },
65
+ routes: {
66
+ "/404": false,
67
+ "/docs": { priority: 0.8, lastModified: "2026-07-18" },
68
+ },
69
+ },
70
+ };
71
+ ```
72
+
73
+ The sitemap config types are available from `@askrjs/cli/ssg` for projects that
74
+ want an explicit annotation.
75
+
76
+ Successful and incrementally skipped concrete routes are included by default.
77
+ Failed routes and wildcard templates are excluded. Route settings support a
78
+ canonical URL override, `lastModified`, `changeFrequency`, `priority`, and
79
+ `hreflang` alternates. Use `resolve(route)` for data-driven metadata or return
80
+ `false` to exclude a route. Set `sitemap: false` for an intentional opt-out.
81
+
82
+ The CLI validates real calendar dates, bounds asynchronous metadata resolution,
83
+ and enforces the sitemap protocol limits of 50,000 URLs and 50 MB per file.
84
+ Larger sites receive deterministic sitemap chunks plus a sitemap index. It also
85
+ creates or updates `robots.txt` while preserving unrelated directives and tracks
86
+ owned files so stale chunks and previous output paths are removed. Full and
87
+ incremental builds publish through a sibling staging directory, so route output,
88
+ metadata, assets, and sitemap artifacts change together or not at all.
89
+
53
90
  ## OpenAPI artifacts
54
91
 
55
92
  `askr openapi` loads a TypeScript module whose default export exposes
package/dist/add.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ //#region src/file-changes.d.ts
2
+ interface FileChange {
3
+ readonly filePath: string;
4
+ readonly content: string;
5
+ }
6
+ interface FileChangeWriterOptions {
7
+ readonly replace?: (temporaryPath: string, filePath: string) => Promise<void>;
8
+ }
9
+ declare function writeFileChanges(changes: readonly FileChange[], options?: FileChangeWriterOptions): Promise<void>;
10
+ //#endregion
11
+ //#region src/bin/add.d.ts
12
+ type CliIo = Pick<Console, "error" | "log">;
13
+ type WriteChanges = typeof writeFileChanges;
14
+ declare function runAddCli(args?: string[], io?: CliIo, writeChanges?: WriteChanges): Promise<number>;
15
+ //#endregion
16
+ export { runAddCli };
package/dist/add.js CHANGED
@@ -142,32 +142,34 @@ function parseArgs(args) {
142
142
  const arg = args[index];
143
143
  if (arg === "--help" || arg === "-h") parsed.help = true;
144
144
  else if (arg === "--force") parsed.force = true;
145
- else if (arg === "--branch") if (index + 1 >= args.length) parsed.errors.push("Missing value for --branch");
145
+ else if (arg === "--branch") if (index + 1 >= args.length || args[index + 1].startsWith("-")) parsed.errors.push("Missing value for --branch");
146
146
  else {
147
147
  parsed.branch = args[index + 1];
148
148
  index += 1;
149
149
  }
150
- else if (arg === "--cwd") if (index + 1 >= args.length) parsed.errors.push("Missing value for --cwd");
150
+ else if (arg === "--cwd") if (index + 1 >= args.length || args[index + 1].startsWith("-")) parsed.errors.push("Missing value for --cwd");
151
151
  else {
152
152
  parsed.cwd = args[index + 1];
153
153
  index += 1;
154
154
  }
155
- else if (arg === "--title") if (index + 1 >= args.length) parsed.errors.push("Missing value for --title");
155
+ else if (arg === "--title") if (index + 1 >= args.length || args[index + 1].startsWith("-")) parsed.errors.push("Missing value for --title");
156
156
  else {
157
157
  parsed.title = args[index + 1].trim();
158
158
  index += 1;
159
159
  }
160
- else if (arg === "--route") if (index + 1 >= args.length) parsed.errors.push("Missing value for --route");
160
+ else if (arg === "--route") if (index + 1 >= args.length || args[index + 1].startsWith("-")) parsed.errors.push("Missing value for --route");
161
161
  else {
162
162
  parsed.routePath = args[index + 1].trim();
163
163
  index += 1;
164
164
  }
165
+ else if (arg.startsWith("-")) parsed.errors.push(`Unknown option: ${arg}`);
165
166
  else positional.push(arg);
166
167
  }
167
168
  return {
168
169
  ...parsed,
169
170
  command: positional[0] || "",
170
- name: positional[1] || ""
171
+ name: positional[1] || "",
172
+ positionals: positional
171
173
  };
172
174
  }
173
175
  async function pathExists(filePath) {
@@ -322,16 +324,12 @@ function toImportSpecifier(fromFile, toFile) {
322
324
  }
323
325
  function createUpdatedRouteFile(routeFileContent, options) {
324
326
  const normalized = routeFileContent.replace(/\r\n/g, "\n");
325
- const lines = normalized.split("\n");
326
- const lastImportIndex = lines.reduce((currentIndex, line, index) => line.startsWith("import ") ? index : currentIndex, -1);
327
- if (lastImportIndex === -1) throw new Error("Unsupported route file: could not find import block.");
328
327
  const importLine = `import ${options.componentName} from '${options.importSpecifier}';`;
329
328
  if (normalized.includes(importLine) || normalized.includes(`route('${options.routePath}',`) || normalized.includes(`route("${options.routePath}",`)) throw new Error(`Route '${options.routePath}' is already registered.`);
330
- lines.splice(lastImportIndex + 1, 0, importLine);
331
- const closingIndex = lines.map((line) => line.trim()).lastIndexOf("}");
332
- if (closingIndex === -1) throw new Error("Unsupported route file: could not find registration function body.");
333
- lines.splice(closingIndex, 0, ` route('${options.routePath}', ${options.componentName});`);
334
- return `${lines.join("\n")}\n`;
329
+ const importMarker = "// askr:imports";
330
+ const routeMarker = " // askr:routes";
331
+ if (normalized.split(importMarker).length !== 2 || normalized.split(routeMarker).length !== 2) throw new Error("Unsupported route file: expected exactly one // askr:imports and // askr:routes marker.");
332
+ return `${normalized.replace(importMarker, `${importMarker}\n${importLine}`).replace(routeMarker, `${routeMarker}\n route('${options.routePath}', ${options.componentName});`).replace(/\n+$/, "")}\n`;
335
333
  }
336
334
  function renderPageFile(options) {
337
335
  return [
@@ -378,7 +376,7 @@ function renderPageFile(options) {
378
376
  ""
379
377
  ].join("\n");
380
378
  }
381
- async function addPage(parsed, io) {
379
+ async function addPage(parsed, io, writeChanges) {
382
380
  if (!parsed.name) {
383
381
  io.error("Page name is required.");
384
382
  return 1;
@@ -429,14 +427,18 @@ async function addPage(parsed, io) {
429
427
  return 1;
430
428
  }
431
429
  try {
432
- await fs.mkdir(path.dirname(pageFile), { recursive: true });
433
- await fs.writeFile(pageFile, renderPageFile({
434
- badge: branchConfig.badge,
435
- componentName,
436
- routePath,
437
- title
438
- }), "utf8");
439
- await fs.writeFile(routesFile, updatedRoutes, "utf8");
430
+ await writeChanges([{
431
+ filePath: pageFile,
432
+ content: renderPageFile({
433
+ badge: branchConfig.badge,
434
+ componentName,
435
+ routePath,
436
+ title
437
+ })
438
+ }, {
439
+ filePath: routesFile,
440
+ content: updatedRoutes
441
+ }]);
440
442
  } catch (error) {
441
443
  io.error("Failed to write generated page artifacts.");
442
444
  io.error(error instanceof Error ? error.message : String(error));
@@ -448,7 +450,7 @@ async function addPage(parsed, io) {
448
450
  io.log(` Router: ${path.relative(projectRoot, routesFile).replace(/\\/g, "/")}`);
449
451
  return 0;
450
452
  }
451
- async function addAction(parsed, io) {
453
+ async function addAction(parsed, io, writeChanges) {
452
454
  if (!parsed.name) {
453
455
  io.error("Action name is required.");
454
456
  return 1;
@@ -489,7 +491,7 @@ async function addAction(parsed, io) {
489
491
  filePath: descriptorFile,
490
492
  content: descriptor
491
493
  });
492
- await writeFileChanges([
494
+ await writeChanges([
493
495
  {
494
496
  filePath: descriptorFile,
495
497
  content: descriptor
@@ -529,7 +531,7 @@ async function addAction(parsed, io) {
529
531
  io.log(` Test: ${path.relative(projectRoot, testFile).replace(/\\/g, "/")}`);
530
532
  return 0;
531
533
  }
532
- async function runAddCli(args = process.argv.slice(2), io = console) {
534
+ async function runAddCli(args = process.argv.slice(2), io = console, writeChanges = writeFileChanges) {
533
535
  const parsed = parseArgs(args);
534
536
  if (parsed.errors.length > 0) {
535
537
  for (const message of parsed.errors) io.error(message);
@@ -539,8 +541,12 @@ async function runAddCli(args = process.argv.slice(2), io = console) {
539
541
  io.log(helpText());
540
542
  return 0;
541
543
  }
542
- if (parsed.command === "page") return addPage(parsed, io);
543
- if (parsed.command === "action") return addAction(parsed, io);
544
+ if (parsed.positionals.length !== 2 || !parsed.name) {
545
+ io.error("A command and exactly one generator name are required.");
546
+ return 1;
547
+ }
548
+ if (parsed.command === "page") return addPage(parsed, io, writeChanges);
549
+ if (parsed.command === "action") return addAction(parsed, io, writeChanges);
544
550
  io.error(`Unknown add command: ${parsed.command}`);
545
551
  io.error("Run `askr add --help` to see available commands.");
546
552
  return 1;
@@ -552,5 +558,3 @@ async function main() {
552
558
  if (isDirectExecution(import.meta.url)) main();
553
559
  //#endregion
554
560
  export { runAddCli };
555
-
556
- //# sourceMappingURL=add.js.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ //#region src/bin/cli.d.ts
2
+ type CliIo = Pick<Console, "error" | "log">;
3
+ declare function runCli(args?: string[], io?: CliIo): Promise<number>;
4
+ //#endregion
5
+ export { runCli };
package/dist/cli.js CHANGED
@@ -116,5 +116,3 @@ async function main() {
116
116
  if (isDirectExecution(import.meta.url)) main();
117
117
  //#endregion
118
118
  export { runCli };
119
-
120
- //# sourceMappingURL=cli.js.map
@@ -0,0 +1,5 @@
1
+ //#region src/bin/create.d.ts
2
+ type CliIo = Pick<Console, "error" | "log">;
3
+ declare function runCreateCli(args?: string[], io?: CliIo): Promise<number>;
4
+ //#endregion
5
+ export { runCreateCli };
package/dist/create.js CHANGED
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as isDirectExecution } from "./is-direct-execution-Cdlr-ZUl.js";
3
- import { t as installBundledSkills } from "./skills-Bh0mEhIx.js";
3
+ import { t as installBundledSkills } from "./skills-B7CbWur9.js";
4
+ import { n as publishStagedDirectory, t as createSiblingStage } from "./directory-swap-DWoHtx7C.js";
4
5
  import fs from "node:fs/promises";
5
6
  import path from "node:path";
6
7
  import { fileURLToPath } from "node:url";
7
8
  import { spawnSync } from "node:child_process";
9
+ import { isUtf8 } from "node:buffer";
8
10
  import readline from "node:readline";
9
11
  //#region src/bin/create.ts
10
12
  const TEMPLATE_LABELS = {
@@ -396,8 +398,8 @@ const TEMPLATE_INSPECT_PATHS = {
396
398
  ssr: [
397
399
  "AGENTS.md",
398
400
  "src/main.tsx",
399
- "src/server-entry.tsx",
400
- "src/pages/_routes.tsx",
401
+ "src/entry-server.tsx",
402
+ "src/routes.tsx",
401
403
  "server.ts"
402
404
  ],
403
405
  ssg: [
@@ -432,7 +434,7 @@ const TEMPLATE_GOLDEN_EXAMPLES = {
432
434
  ],
433
435
  ssr: [
434
436
  "src/main.tsx",
435
- "src/pages/_routes.tsx",
437
+ "src/routes.tsx",
436
438
  "server.ts"
437
439
  ],
438
440
  ssg: [
@@ -462,7 +464,7 @@ function helpText() {
462
464
  "askr create - Project scaffolding for Askr",
463
465
  "",
464
466
  "Usage:",
465
- " askr create [template] <name> [--prompt <text>] [--no-install] [--no-skills]",
467
+ " askr create [template] <name> [--dir <path>] [--prompt <text>] [--no-install] [--no-skills]",
466
468
  " askr create --prompt <text> [name] [--no-install] [--no-skills]",
467
469
  "",
468
470
  "Templates:",
@@ -470,6 +472,7 @@ function helpText() {
470
472
  "",
471
473
  "Options:",
472
474
  " --prompt <text> Infer the best template from a product prompt and emit a builder blueprint",
475
+ " --dir <path> Explicit output directory (defaults to ./<name>)",
473
476
  " --no-install Skip dependency installation",
474
477
  " --no-skills Skip bundled Askr skill installation into skills/",
475
478
  " --help, -h Show help",
@@ -495,6 +498,8 @@ async function prompt(question) {
495
498
  }
496
499
  function detectPm() {
497
500
  const ua = process.env.npm_config_user_agent || "";
501
+ if (ua.startsWith("pnpm")) return "pnpm";
502
+ if (ua.startsWith("bun")) return "bun";
498
503
  if (ua.startsWith("yarn")) return "yarn";
499
504
  if (ua.startsWith("npm")) return "npm";
500
505
  return "npm";
@@ -522,7 +527,12 @@ async function copyDir(src, dest, replacements) {
522
527
  await copyDir(srcPath, destPath, replacements);
523
528
  continue;
524
529
  }
525
- const replaced = (await fs.readFile(srcPath)).toString("utf8").replace(/\{\{\s*appName\s*\}\}/g, replacements.appName).replace(/\{\{appName\}\}/g, replacements.appName);
530
+ const buffer = await fs.readFile(srcPath);
531
+ if (!isUtf8(buffer)) {
532
+ await fs.writeFile(destPath, buffer);
533
+ continue;
534
+ }
535
+ const replaced = buffer.toString("utf8").replace(/\{\{\s*appName\s*\}\}/g, replacements.appName).replace(/\{\{appName\}\}/g, replacements.appName);
526
536
  await fs.writeFile(destPath, replaced, "utf8");
527
537
  }
528
538
  }
@@ -548,17 +558,24 @@ function parseArgs(args) {
548
558
  let help = false;
549
559
  let promptText = "";
550
560
  let skills = true;
561
+ let targetDir = "";
551
562
  const errors = [];
552
563
  for (let i = 0; i < args.length; i += 1) {
553
564
  const arg = args[i];
554
565
  if (arg === "--no-install") install = false;
555
566
  else if (arg === "--no-skills") skills = false;
556
- else if (arg === "--prompt") if (i + 1 >= args.length) errors.push("Missing value for --prompt");
567
+ else if (arg === "--prompt") if (i + 1 >= args.length || args[i + 1].startsWith("-")) errors.push("Missing value for --prompt");
557
568
  else {
558
569
  promptText = normalizePromptText(args[i + 1]);
559
570
  i += 1;
560
571
  }
572
+ else if (arg === "--dir") if (i + 1 >= args.length || args[i + 1].startsWith("-")) errors.push("Missing value for --dir");
573
+ else {
574
+ targetDir = args[i + 1];
575
+ i += 1;
576
+ }
561
577
  else if (arg === "--help" || arg === "-h") help = true;
578
+ else if (arg.startsWith("-")) errors.push(`Unknown option: ${arg}`);
562
579
  else positional.push(arg);
563
580
  }
564
581
  return {
@@ -567,9 +584,15 @@ function parseArgs(args) {
567
584
  help,
568
585
  promptText,
569
586
  skills,
570
- errors
587
+ errors,
588
+ targetDir
571
589
  };
572
590
  }
591
+ function validateAppName(name) {
592
+ if (name.length > 214) return "App name must be 214 characters or fewer";
593
+ if (!/^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/.test(name)) return "App name must be a lowercase npm package name without path separators";
594
+ if (/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])$/.test(name)) return `App name is reserved on Windows: ${name}`;
595
+ }
573
596
  function normalizePromptText(promptText) {
574
597
  return promptText.trim().replace(/\s+/g, " ");
575
598
  }
@@ -810,7 +833,17 @@ async function runCreateCli(args = process.argv.slice(2), io = console) {
810
833
  io.error("App name is required");
811
834
  return 1;
812
835
  }
813
- const target = path.resolve(process.cwd(), name);
836
+ const nameError = validateAppName(name);
837
+ if (nameError) {
838
+ io.error(nameError);
839
+ return 1;
840
+ }
841
+ const expectedPositionals = explicitTemplate ? 2 : 1;
842
+ if (parsed.positional.length > expectedPositionals) {
843
+ io.error("Too many positional arguments");
844
+ return 1;
845
+ }
846
+ const target = path.resolve(process.cwd(), parsed.targetDir || name);
814
847
  const templateDir = await findTemplateDir(templateType);
815
848
  try {
816
849
  await fs.access(templateDir);
@@ -819,7 +852,12 @@ async function runCreateCli(args = process.argv.slice(2), io = console) {
819
852
  return 1;
820
853
  }
821
854
  try {
822
- if (await fs.stat(target).catch(() => null)) {
855
+ const stat = await fs.stat(target).catch(() => null);
856
+ if (stat) {
857
+ if (!stat.isDirectory()) {
858
+ io.error(`Target exists and is not a directory: ${target}`);
859
+ return 1;
860
+ }
823
861
  if ((await fs.readdir(target).catch(() => [])).length > 0) {
824
862
  io.error(`Directory ${target} already exists and is not empty.`);
825
863
  return 1;
@@ -844,28 +882,48 @@ async function runCreateCli(args = process.argv.slice(2), io = console) {
844
882
  }
845
883
  io.log(`Creating ${TEMPLATE_LABELS[templateType]} project: ${name}...`);
846
884
  io.log("");
885
+ let stagingTarget;
886
+ try {
887
+ stagingTarget = await createSiblingStage(target, "askr-create");
888
+ } catch (error) {
889
+ io.error("Failed to prepare project output");
890
+ io.error(error instanceof Error ? error.message : String(error));
891
+ return 1;
892
+ }
847
893
  try {
848
- await copyDir(templateDir, target, { appName: name });
894
+ await copyDir(templateDir, stagingTarget, { appName: name });
849
895
  } catch (error) {
896
+ await fs.rm(stagingTarget, {
897
+ recursive: true,
898
+ force: true
899
+ });
850
900
  io.error("Failed to copy template");
851
901
  io.error(error instanceof Error ? error.message : String(error));
852
902
  return 1;
853
903
  }
854
904
  try {
855
- await writeBlueprintFiles(target, blueprint);
905
+ await writeBlueprintFiles(stagingTarget, blueprint);
856
906
  } catch (error) {
907
+ await fs.rm(stagingTarget, {
908
+ recursive: true,
909
+ force: true
910
+ });
857
911
  io.error("Failed to write project blueprint");
858
912
  io.error(error instanceof Error ? error.message : String(error));
859
913
  return 1;
860
914
  }
861
915
  if (parsed.skills) try {
862
916
  const { bundledNames, targetSkillsDir } = await installBundledSkills({
863
- cwd: target,
917
+ cwd: stagingTarget,
864
918
  force: true
865
919
  });
866
920
  io.log(`Installed ${bundledNames.length} bundled Askr skills into ${path.basename(targetSkillsDir)}/`);
867
921
  io.log("");
868
922
  } catch (error) {
923
+ await fs.rm(stagingTarget, {
924
+ recursive: true,
925
+ force: true
926
+ });
869
927
  io.error("Failed to install bundled Askr skills");
870
928
  io.error(error instanceof Error ? error.message : String(error));
871
929
  return 1;
@@ -874,35 +932,57 @@ async function runCreateCli(args = process.argv.slice(2), io = console) {
874
932
  io.log("Skipping bundled Askr skill installation (--no-skills)");
875
933
  io.log("");
876
934
  }
935
+ const pm = detectPm();
877
936
  if (!parsed.install) {
937
+ try {
938
+ await publishStagedDirectory(stagingTarget, target);
939
+ } catch (error) {
940
+ await fs.rm(stagingTarget, {
941
+ recursive: true,
942
+ force: true
943
+ });
944
+ io.error("Failed to publish generated project");
945
+ io.error(error instanceof Error ? error.message : String(error));
946
+ return 1;
947
+ }
878
948
  io.log("Skipping dependency installation (--no-install)");
879
949
  io.log("");
880
950
  io.log("Next steps:");
881
951
  io.log(` cd ${name}`);
882
952
  io.log(" review .askr/builder-brief.md");
883
953
  if (!parsed.skills) io.log(" askr skills install");
884
- io.log(" npm install");
885
- io.log(" npm run dev");
954
+ io.log(` ${pm} install`);
955
+ io.log(` ${pm} run dev`);
886
956
  return 0;
887
957
  }
888
- const pm = detectPm();
889
958
  io.log(`Installing dependencies with ${pm}...`);
890
959
  io.log("");
891
960
  const result = spawnSync(pm, ["install"], {
892
- cwd: target,
961
+ cwd: stagingTarget,
893
962
  stdio: "inherit",
894
963
  shell: process.platform === "win32"
895
964
  });
965
+ if (result.status !== 0) {
966
+ await fs.rm(stagingTarget, {
967
+ recursive: true,
968
+ force: true
969
+ });
970
+ io.error(`Dependency installation with ${pm} failed; no project files were published.`);
971
+ if (result.error) io.error(result.error.message);
972
+ return 1;
973
+ }
896
974
  io.log("");
897
975
  io.log(`Success! Created ${name}`);
898
976
  io.log("");
899
- if (result.status !== 0) {
900
- io.log("Dependency installation failed. Please run manually:");
901
- io.log(` cd ${name}`);
902
- io.log(" review .askr/builder-brief.md");
903
- if (!parsed.skills) io.log(" askr skills install");
904
- io.log(` ${pm} install`);
905
- io.log(` ${pm} run dev`);
977
+ try {
978
+ await publishStagedDirectory(stagingTarget, target);
979
+ } catch (error) {
980
+ await fs.rm(stagingTarget, {
981
+ recursive: true,
982
+ force: true
983
+ });
984
+ io.error("Failed to publish generated project");
985
+ io.error(error instanceof Error ? error.message : String(error));
906
986
  return 1;
907
987
  }
908
988
  io.log("Next steps:");
@@ -919,5 +999,3 @@ async function main() {
919
999
  if (isDirectExecution(import.meta.url)) main();
920
1000
  //#endregion
921
1001
  export { runCreateCli };
922
-
923
- //# sourceMappingURL=create.js.map
@@ -0,0 +1,35 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/directory-swap.ts
5
+ async function exists(target) {
6
+ return Boolean(await fs.stat(target).catch(() => null));
7
+ }
8
+ async function createSiblingStage(target, label) {
9
+ const resolved = path.resolve(target);
10
+ const parent = path.dirname(resolved);
11
+ await fs.mkdir(parent, { recursive: true });
12
+ return fs.mkdtemp(path.join(parent, `.${path.basename(resolved)}.${label}-`));
13
+ }
14
+ async function publishStagedDirectory(stage, target) {
15
+ const resolvedTarget = path.resolve(target);
16
+ const backup = path.join(path.dirname(resolvedTarget), `.${path.basename(resolvedTarget)}.askr-backup-${randomUUID()}`);
17
+ const hadTarget = await exists(resolvedTarget);
18
+ let movedTarget = false;
19
+ try {
20
+ if (hadTarget) {
21
+ await fs.rename(resolvedTarget, backup);
22
+ movedTarget = true;
23
+ }
24
+ await fs.rename(stage, resolvedTarget);
25
+ } catch (error) {
26
+ if (movedTarget && !await exists(resolvedTarget) && await exists(backup)) await fs.rename(backup, resolvedTarget);
27
+ throw error;
28
+ }
29
+ if (movedTarget) await fs.rm(backup, {
30
+ recursive: true,
31
+ force: true
32
+ }).catch(() => void 0);
33
+ }
34
+ //#endregion
35
+ export { publishStagedDirectory as n, createSiblingStage as t };
@@ -321,5 +321,3 @@ async function discoverProject(options) {
321
321
  }
322
322
  //#endregion
323
323
  export { discoverProject };
324
-
325
- //# sourceMappingURL=discovery-Difb7Y4G.js.map
@@ -0,0 +1,5 @@
1
+ //#region src/bin/generate.d.ts
2
+ type Io = Pick<Console, "error" | "log">;
3
+ declare function runGenerateCli(args: string[], io?: Io): Promise<number>;
4
+ //#endregion
5
+ export { runGenerateCli };