create-next-pro-cli 0.1.30 → 0.1.32

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 (29) hide show
  1. package/README.md +16 -6
  2. package/create-next-pro-completion.sh +4 -3
  3. package/create-next-pro-completion.zsh +3 -3
  4. package/dist/bin.bun.js +514 -118
  5. package/dist/bin.node.js +579 -135
  6. package/dist/bin.node.js.map +1 -1
  7. package/package.json +6 -5
  8. package/templates/Page/page-ui.tsx +2 -2
  9. package/templates/Page/page-ui.user.tsx +17 -0
  10. package/templates/Projects/default/.agents/skills/create-next-pro-addapi/SKILL.md +61 -0
  11. package/templates/Projects/default/.agents/skills/create-next-pro-addcomponent/SKILL.md +67 -0
  12. package/templates/Projects/default/.agents/skills/create-next-pro-addlanguage/SKILL.md +68 -0
  13. package/templates/Projects/default/.agents/skills/create-next-pro-addlib/SKILL.md +65 -0
  14. package/templates/Projects/default/.agents/skills/create-next-pro-addpage/SKILL.md +89 -0
  15. package/templates/Projects/default/.agents/skills/create-next-pro-addtext/SKILL.md +63 -0
  16. package/templates/Projects/default/.agents/skills/create-next-pro-create-project/SKILL.md +76 -0
  17. package/templates/Projects/default/.agents/skills/create-next-pro-rmpage/SKILL.md +67 -0
  18. package/templates/Projects/default/.github/workflows/quality.yml +2 -1
  19. package/templates/Projects/default/AGENTS.md +91 -0
  20. package/templates/Projects/default/README.md +29 -9
  21. package/templates/Projects/default/bun.lock +114 -106
  22. package/templates/Projects/default/package.json +7 -5
  23. package/templates/Projects/default/pnpm-workspace.yaml +2 -1
  24. package/templates/Projects/default/scripts/audit-policy.ts +376 -0
  25. package/templates/Projects/default/scripts/audit.ts +72 -3
  26. package/templates/Projects/default/scripts/package-manager.ts +37 -0
  27. package/templates/Projects/default/tests/consumer/validate-template.ts +2 -0
  28. package/templates/Projects/default/tests/unit/audit-policy.test.ts +152 -0
  29. package/templates/Projects/default/tests/unit/template-baseline.test.ts +9 -0
package/dist/bin.node.js CHANGED
@@ -6,14 +6,124 @@ import path10 from "path";
6
6
  // src/cli/completion.ts
7
7
  import path2 from "path";
8
8
 
9
+ // src/core/contracts.ts
10
+ var CliError = class extends Error {
11
+ exitCode;
12
+ code;
13
+ hint;
14
+ scope;
15
+ path;
16
+ constructor(message, options = {}) {
17
+ super(message);
18
+ this.name = "CliError";
19
+ if (typeof options === "number") {
20
+ this.exitCode = options;
21
+ this.code = "FILESYSTEM_ERROR";
22
+ } else {
23
+ this.exitCode = options.exitCode ?? 1;
24
+ this.code = options.code ?? "FILESYSTEM_ERROR";
25
+ this.hint = options.hint;
26
+ this.scope = options.scope;
27
+ this.path = options.path;
28
+ }
29
+ }
30
+ };
31
+
32
+ // src/core/page-area.ts
33
+ var PAGE_AREAS = ["public", "user"];
34
+ function isPageArea(value) {
35
+ return PAGE_AREAS.includes(value);
36
+ }
37
+ function parseAreaOption(args) {
38
+ const remaining = args.length > 0 ? [args[0]] : [];
39
+ let area;
40
+ for (let index = 1; index < args.length; index += 1) {
41
+ const argument = args[index];
42
+ if (argument.startsWith("--area=")) {
43
+ throw new CliError(
44
+ "The --area option must use a separate public or user value.",
45
+ {
46
+ code: "INVALID_ARGUMENT",
47
+ hint: "Use --area public or --area user."
48
+ }
49
+ );
50
+ }
51
+ if (argument !== "--area") {
52
+ remaining.push(argument);
53
+ continue;
54
+ }
55
+ if (area) {
56
+ throw new CliError("The --area option can only be provided once.", {
57
+ code: "INVALID_ARGUMENT"
58
+ });
59
+ }
60
+ const value = args[index + 1];
61
+ if (!value || value.startsWith("-")) {
62
+ throw new CliError("The --area option requires public or user.", {
63
+ code: "INVALID_ARGUMENT",
64
+ hint: "Use --area public or --area user."
65
+ });
66
+ }
67
+ if (!isPageArea(value)) {
68
+ throw new CliError(`Unsupported page area: ${value}.`, {
69
+ code: "INVALID_ARGUMENT",
70
+ hint: "Use --area public or --area user."
71
+ });
72
+ }
73
+ area = value;
74
+ index += 1;
75
+ }
76
+ return { area, args: remaining };
77
+ }
78
+ function requirePageArea(area, command) {
79
+ if (area) return area;
80
+ throw new CliError(`The ${command} command requires --area public or user.`, {
81
+ code: "INVALID_ARGUMENT",
82
+ hint: `Run ${command} with --area public or --area user.`
83
+ });
84
+ }
85
+ function areaRouteGroup(area) {
86
+ return `(${area})`;
87
+ }
88
+
9
89
  // src/core/page-catalog.ts
10
90
  import path from "path";
11
91
  function isRouteGroup(segment) {
12
92
  return segment.startsWith("(") && segment.endsWith(")");
13
93
  }
14
- async function discoverPages(projectRoot, fs) {
15
- const appRoot = path.join(projectRoot, "src", "app", "[locale]");
16
- const candidates = [];
94
+ function logicalSegments(relative) {
95
+ return relative.filter((segment) => !isRouteGroup(segment));
96
+ }
97
+ function issueForRoute(projectRoot, directory, relative) {
98
+ const segments = logicalSegments(relative);
99
+ if (segments.length === 0 || segments.some(
100
+ (segment) => segment.startsWith("_") || segment.startsWith("[")
101
+ )) {
102
+ return void 0;
103
+ }
104
+ const first = relative[0];
105
+ if (!first || !isRouteGroup(first)) {
106
+ return {
107
+ logicalName: segments.join("."),
108
+ reason: "ungrouped",
109
+ routeDirectories: [path.relative(projectRoot, directory)]
110
+ };
111
+ }
112
+ const groupName = first.slice(1, -1);
113
+ if (!isPageArea(groupName) || relative.slice(1).some(isRouteGroup)) {
114
+ return {
115
+ logicalName: segments.join("."),
116
+ reason: "unsupported-route-group",
117
+ routeDirectories: [path.relative(projectRoot, directory)]
118
+ };
119
+ }
120
+ return void 0;
121
+ }
122
+ async function discoverPageCatalog(projectRoot, fs) {
123
+ const localizedRoot = path.join(projectRoot, "src", "app", "[locale]");
124
+ const appRoot = fs.exists(localizedRoot) ? localizedRoot : path.join(projectRoot, "src", "app");
125
+ const rawCandidates = [];
126
+ const issues = [];
17
127
  async function visit(directory, relative = []) {
18
128
  let entries;
19
129
  try {
@@ -22,26 +132,32 @@ async function discoverPages(projectRoot, fs) {
22
132
  return;
23
133
  }
24
134
  if (entries.some((entry) => entry.isFile && entry.name === "page.tsx")) {
25
- const routeSegments = relative.filter(
26
- (segment) => !isRouteGroup(segment)
27
- );
28
- if (routeSegments.length > 0 && !routeSegments.some(
29
- (segment) => segment.startsWith("_") || segment.startsWith("[")
30
- )) {
31
- const logicalName = routeSegments.join(".");
32
- candidates.push({
33
- logicalName,
34
- routeSegments,
35
- routeDirectory: directory,
36
- uiDirectory: path.join(projectRoot, "src", "ui", ...routeSegments),
37
- messageFile: path.join(
38
- projectRoot,
39
- "messages",
40
- "{locale}",
41
- `${routeSegments[0]}.json`
42
- ),
43
- messageKey: routeSegments.length > 1 ? routeSegments.at(-1) : void 0
44
- });
135
+ const issue = issueForRoute(projectRoot, directory, relative);
136
+ if (issue) {
137
+ issues.push(issue);
138
+ } else if (relative.length > 1) {
139
+ const area = relative[0].slice(1, -1);
140
+ const routeSegments = relative.slice(1);
141
+ if (routeSegments.length > 0 && !routeSegments.some(
142
+ (segment) => segment.startsWith("_") || segment.startsWith("[")
143
+ )) {
144
+ const logicalName = routeSegments.join(".");
145
+ rawCandidates.push({
146
+ id: `${area}:${logicalName}`,
147
+ area,
148
+ logicalName,
149
+ routeSegments,
150
+ routeDirectory: directory,
151
+ uiDirectory: path.join(projectRoot, "src", "ui", ...routeSegments),
152
+ messageFile: path.join(
153
+ projectRoot,
154
+ "messages",
155
+ "{locale}",
156
+ `${routeSegments[0]}.json`
157
+ ),
158
+ messageKey: routeSegments.length > 1 ? routeSegments.at(-1) : void 0
159
+ });
160
+ }
45
161
  }
46
162
  }
47
163
  for (const entry of entries) {
@@ -54,9 +170,59 @@ async function discoverPages(projectRoot, fs) {
54
170
  }
55
171
  }
56
172
  await visit(appRoot);
57
- return candidates.sort(
173
+ const candidatesByName = /* @__PURE__ */ new Map();
174
+ for (const candidate of rawCandidates) {
175
+ const entries = candidatesByName.get(candidate.logicalName) ?? [];
176
+ entries.push(candidate);
177
+ candidatesByName.set(candidate.logicalName, entries);
178
+ }
179
+ for (const [logicalName, candidates2] of candidatesByName) {
180
+ if (candidates2.length < 2) continue;
181
+ issues.push({
182
+ logicalName,
183
+ reason: "duplicate-logical-route",
184
+ routeDirectories: candidates2.map(
185
+ (candidate) => path.relative(projectRoot, candidate.routeDirectory)
186
+ )
187
+ });
188
+ }
189
+ const invalidNames = new Set(issues.map((issue) => issue.logicalName));
190
+ const candidates = rawCandidates.filter((candidate) => !invalidNames.has(candidate.logicalName)).sort(
191
+ (left, right) => left.area.localeCompare(right.area) || left.logicalName.localeCompare(right.logicalName)
192
+ );
193
+ issues.sort(
58
194
  (left, right) => left.logicalName.localeCompare(right.logicalName)
59
195
  );
196
+ return { candidates, issues };
197
+ }
198
+ function routeIssue(catalog, logicalName) {
199
+ return catalog.issues.find((issue) => issue.logicalName === logicalName);
200
+ }
201
+ function assertConsistentLogicalRoute(catalog, logicalName) {
202
+ const issue = routeIssue(catalog, logicalName);
203
+ if (!issue) return;
204
+ throw new CliError(`Page route "${logicalName}" is inconsistent.`, {
205
+ code: "INCONSISTENT_ROUTE",
206
+ scope: "project",
207
+ path: issue.routeDirectories.join(", "),
208
+ hint: "Move the route into exactly one of the (public) or (user) areas before retrying."
209
+ });
210
+ }
211
+ function resolvePageCandidate(catalog, logicalName, area) {
212
+ assertConsistentLogicalRoute(catalog, logicalName);
213
+ const candidate = catalog.candidates.find(
214
+ (entry) => entry.logicalName === logicalName && entry.area === area
215
+ );
216
+ if (candidate) return candidate;
217
+ const otherArea = catalog.candidates.find(
218
+ (entry) => entry.logicalName === logicalName
219
+ );
220
+ throw new CliError(`Page not found in the ${area} area: ${logicalName}.`, {
221
+ code: "TARGET_NOT_FOUND",
222
+ scope: "project",
223
+ path: logicalName.replaceAll(".", "/"),
224
+ hint: otherArea ? `The page exists in the ${otherArea.area} area.` : void 0
225
+ });
60
226
  }
61
227
 
62
228
  // src/cli/completion.ts
@@ -75,6 +241,7 @@ var PUBLIC_COMMANDS = [
75
241
  ];
76
242
  var OPTIONS = {
77
243
  addpage: [
244
+ "--area",
78
245
  "--layout",
79
246
  "--page",
80
247
  "--loading",
@@ -94,22 +261,57 @@ async function directories(root, context) {
94
261
  return [];
95
262
  }
96
263
  }
97
- async function completionCandidates(command, context) {
98
- if (!command) return [...PUBLIC_COMMANDS];
264
+ function selectedArea(args) {
265
+ const index = args.lastIndexOf("--area");
266
+ if (index < 0) return void 0;
267
+ const value = args[index + 1];
268
+ return value && isPageArea(value) ? value : void 0;
269
+ }
270
+ function uniqueSorted(candidates) {
271
+ return [...new Set(candidates)].sort(
272
+ (left, right) => left.localeCompare(right)
273
+ );
274
+ }
275
+ async function completionCandidates(words, context) {
276
+ if (words.length === 0) return [...PUBLIC_COMMANDS];
277
+ const [command, ...args] = words;
278
+ const previous = args.at(-1);
279
+ if (previous === "--area") return [...PAGE_AREAS];
280
+ const area = selectedArea(args);
281
+ const hasArea = args.includes("--area");
99
282
  if (command === "rmpage") {
100
- return (await discoverPages(context.cwd, context.fs)).map(
101
- (candidate) => candidate.logicalName
283
+ if (!area) return hasArea ? [] : ["--area"];
284
+ const catalog = await discoverPageCatalog(context.cwd, context.fs);
285
+ return uniqueSorted(
286
+ catalog.candidates.filter((candidate) => candidate.area === area).map((candidate) => candidate.logicalName)
102
287
  );
103
288
  }
104
- if (command === "addcomponent" || command === "addpage") {
105
- return [
106
- ...OPTIONS[command] ?? [],
289
+ if (command === "addcomponent") {
290
+ const pageOptionIndex = args.findIndex(
291
+ (argument) => argument === "--page" || argument === "-P"
292
+ );
293
+ if (previous === "--page" || previous === "-P") {
294
+ const catalog = await discoverPageCatalog(context.cwd, context.fs);
295
+ return uniqueSorted(
296
+ catalog.candidates.filter((candidate) => !area || candidate.area === area).map((candidate) => candidate.logicalName)
297
+ );
298
+ }
299
+ return uniqueSorted([
300
+ ...OPTIONS.addcomponent ?? [],
301
+ ...pageOptionIndex >= 0 && !hasArea ? ["--area"] : []
302
+ ]);
303
+ }
304
+ if (command === "addpage") {
305
+ return uniqueSorted([
306
+ ...(OPTIONS.addpage ?? []).filter(
307
+ (candidate) => candidate !== "--area" || !hasArea
308
+ ),
107
309
  ...await directories(path2.join(context.cwd, "src", "ui"), context)
108
- ];
310
+ ]);
109
311
  }
110
312
  if (command === "addlanguage")
111
313
  return ["de", "en", "es", "fr", "it", "ja", "pt"];
112
- return OPTIONS[command] ?? [];
314
+ return uniqueSorted(OPTIONS[command] ?? []);
113
315
  }
114
316
 
115
317
  // src/cli/onboarding.ts
@@ -117,31 +319,6 @@ import path4 from "path";
117
319
 
118
320
  // src/core/operations.ts
119
321
  import path3 from "path";
120
-
121
- // src/core/contracts.ts
122
- var CliError = class extends Error {
123
- exitCode;
124
- code;
125
- hint;
126
- scope;
127
- path;
128
- constructor(message, options = {}) {
129
- super(message);
130
- this.name = "CliError";
131
- if (typeof options === "number") {
132
- this.exitCode = options;
133
- this.code = "FILESYSTEM_ERROR";
134
- } else {
135
- this.exitCode = options.exitCode ?? 1;
136
- this.code = options.code ?? "FILESYSTEM_ERROR";
137
- this.hint = options.hint;
138
- this.scope = options.scope;
139
- this.path = options.path;
140
- }
141
- }
142
- };
143
-
144
- // src/core/operations.ts
145
322
  var OperationJournal = class {
146
323
  #events = [];
147
324
  record(event) {
@@ -755,18 +932,64 @@ var addApi = async (args, context) => {
755
932
 
756
933
  // src/lib/addComponent.ts
757
934
  import { join as join3 } from "path";
758
- var addComponent = async (args, context) => {
759
- let componentName = args[1];
760
- const pageIndex = args.findIndex(
761
- (argument) => argument === "-P" || argument === "--page"
935
+ function formatGeneratedTranslations(content) {
936
+ return content.replace(
937
+ /^(\s*)(<(?:h2|p)\b[^>]*>)\{t\("([^"]+)"\)\}(<\/(?:h2|p)>)$/gm,
938
+ (line, indent, opening, key, closing) => line.length <= 80 ? line : `${indent}${opening}
939
+ ${indent} {t("${key}")}
940
+ ${indent}${closing}`
762
941
  );
763
- const pageScope = pageIndex >= 0 ? args[pageIndex + 1] : void 0;
764
- if (pageIndex >= 0 && !pageScope) {
765
- throw new CliError("The --page option requires a page name.", {
766
- code: "INVALID_ARGUMENT"
767
- });
942
+ }
943
+ function parseAddComponentArguments(args) {
944
+ const parsedArea = parseAreaOption(args);
945
+ let componentName;
946
+ let pageScope;
947
+ for (let index = 1; index < parsedArea.args.length; index += 1) {
948
+ const argument = parsedArea.args[index];
949
+ if (argument === "-P" || argument === "--page") {
950
+ if (pageScope) {
951
+ throw new CliError("The --page option can only be provided once.", {
952
+ code: "INVALID_ARGUMENT"
953
+ });
954
+ }
955
+ const value = parsedArea.args[index + 1];
956
+ if (!value || value.startsWith("-")) {
957
+ throw new CliError("The --page option requires a page name.", {
958
+ code: "INVALID_ARGUMENT"
959
+ });
960
+ }
961
+ pageScope = value;
962
+ index += 1;
963
+ continue;
964
+ }
965
+ if (argument.startsWith("-")) {
966
+ throw new CliError(`Unknown addcomponent option: ${argument}.`, {
967
+ code: "INVALID_ARGUMENT"
968
+ });
969
+ }
970
+ if (componentName) {
971
+ throw new CliError(`Unexpected addcomponent argument: ${argument}.`, {
972
+ code: "INVALID_ARGUMENT"
973
+ });
974
+ }
975
+ componentName = argument;
976
+ }
977
+ if (pageScope && !parsedArea.area) {
978
+ requirePageArea(parsedArea.area, "addcomponent --page");
768
979
  }
769
- if (!componentName || componentName.startsWith("-")) {
980
+ if (!pageScope && parsedArea.area) {
981
+ throw new CliError(
982
+ "The --area option is only valid with addcomponent --page.",
983
+ { code: "INVALID_ARGUMENT" }
984
+ );
985
+ }
986
+ return { area: parsedArea.area, componentName, pageScope };
987
+ }
988
+ var addComponent = async (args, context) => {
989
+ const parsed = parseAddComponentArguments(args);
990
+ let { componentName } = parsed;
991
+ const { area, pageScope } = parsed;
992
+ if (!componentName) {
770
993
  if (context.outputMode === "json") {
771
994
  throw new CliError("Component name is required in JSON mode.", {
772
995
  code: "INTERACTIVE_INPUT_REQUIRED",
@@ -810,6 +1033,10 @@ var addComponent = async (args, context) => {
810
1033
  hint: "Run this command from the generated project root."
811
1034
  });
812
1035
  }
1036
+ if (pageScope) {
1037
+ const catalog = await discoverPageCatalog(context.cwd, context.fs);
1038
+ resolvePageCandidate(catalog, pageScope, area);
1039
+ }
813
1040
  const componentNameUpper = capitalize(toIdentifier(componentName));
814
1041
  const templateRoot = join3(context.packageRoot, "templates", "Component");
815
1042
  const componentTemplate = join3(templateRoot, "Component.tsx");
@@ -825,7 +1052,9 @@ var addComponent = async (args, context) => {
825
1052
  await assertSafeTarget(context.cwd, targetDirectory, context.fs);
826
1053
  const componentFile = join3(targetDirectory, `${componentNameUpper}.tsx`);
827
1054
  const translationNamespace = pageScope ?? "_global_ui";
828
- const componentContent = (await context.fs.readText(componentTemplate)).replace(/Component/g, componentNameUpper).replace(/componentPage/g, translationNamespace);
1055
+ const componentContent = formatGeneratedTranslations(
1056
+ (await context.fs.readText(componentTemplate)).replace(/Component/g, componentNameUpper).replace(/componentPage/g, translationNamespace)
1057
+ );
829
1058
  const preparedMessages = [];
830
1059
  if (config.useI18n) {
831
1060
  const messagesRoot = join3(context.cwd, "messages");
@@ -836,6 +1065,7 @@ var addComponent = async (args, context) => {
836
1065
  path: "messages"
837
1066
  });
838
1067
  }
1068
+ await assertSafeTarget(context.cwd, messagesRoot, context.fs);
839
1069
  const templateMessages = JSON.parse(
840
1070
  await context.fs.readText(messagesTemplate)
841
1071
  );
@@ -850,6 +1080,7 @@ var addComponent = async (args, context) => {
850
1080
  for (const locale of locales) {
851
1081
  const messageFile = pageScope ? pageSegments[0] : "_global_ui";
852
1082
  const target = join3(messagesRoot, locale, `${messageFile}.json`);
1083
+ await assertSafeTarget(context.cwd, target, context.fs);
853
1084
  let data = {};
854
1085
  if (context.fs.exists(target)) {
855
1086
  try {
@@ -896,17 +1127,20 @@ var addComponent = async (args, context) => {
896
1127
  const gateway = new MutationGateway(context, context.cwd);
897
1128
  await gateway.mkdir(targetDirectory, {
898
1129
  role: "component-directory",
899
- resource: "directory"
1130
+ resource: "directory",
1131
+ detail: area ? { area } : void 0
900
1132
  });
901
1133
  await gateway.write(componentFile, componentContent, {
902
1134
  role: "ui-component",
903
- preserveExisting: true
1135
+ preserveExisting: true,
1136
+ detail: area ? { area } : void 0
904
1137
  });
905
1138
  for (const item of preparedMessages) {
906
1139
  if (item.exists) {
907
1140
  gateway.unchanged(item.target, {
908
1141
  role: "translation-messages",
909
1142
  detail: {
1143
+ ...area ? { area } : {},
910
1144
  locale: item.locale,
911
1145
  key: `${translationNamespace}.${componentNameUpper}`
912
1146
  }
@@ -915,6 +1149,7 @@ var addComponent = async (args, context) => {
915
1149
  await gateway.write(item.target, item.content, {
916
1150
  role: "translation-messages",
917
1151
  detail: {
1152
+ ...area ? { area } : {},
918
1153
  locale: item.locale,
919
1154
  key: `${translationNamespace}.${componentNameUpper}`
920
1155
  }
@@ -924,8 +1159,9 @@ var addComponent = async (args, context) => {
924
1159
  const mutated = context.operations.snapshot().some((event) => event.action === "created" || event.action === "updated");
925
1160
  return commandResult(context, {
926
1161
  command: "addcomponent",
927
- summary: mutated ? `Added component "${componentNameUpper}" ${pageScope ? `to page "${pageScope}"` : "globally"}.` : `Component "${componentNameUpper}" already exists and was preserved.`,
1162
+ summary: mutated ? `Added component "${componentNameUpper}" ${pageScope ? `to ${area} page "${pageScope}"` : "globally"}.` : pageScope ? `Component "${componentNameUpper}" already exists on ${area} page "${pageScope}" and was preserved.` : `Component "${componentNameUpper}" already exists and was preserved.`,
928
1163
  projectRoot: context.cwd,
1164
+ data: pageScope ? { area, componentName: componentNameUpper, page: pageScope } : { componentName: componentNameUpper },
929
1165
  nextSteps: mutated ? [
930
1166
  {
931
1167
  kind: "review",
@@ -1394,7 +1630,6 @@ var SHORT_FLAGS = {
1394
1630
  };
1395
1631
  function registerMessagesFile(content, locale, fileName) {
1396
1632
  const importPath = `./${locale}/${fileName}.json`;
1397
- if (content.includes(importPath)) return content;
1398
1633
  const declarationIndex = content.indexOf("const messages =");
1399
1634
  const registryMatch = content.match(/const messages = \{([\s\S]*?)\n\};/);
1400
1635
  if (declarationIndex < 0 || !registryMatch) {
@@ -1408,19 +1643,30 @@ function registerMessagesFile(content, locale, fileName) {
1408
1643
  );
1409
1644
  }
1410
1645
  const identifier = toIdentifier(fileName);
1646
+ const property = identifier === fileName ? ` ${identifier},` : ` ${JSON.stringify(fileName)}: ${identifier},`;
1647
+ if (content.includes(importPath)) {
1648
+ if (registryMatch[1].split(/\r?\n/).includes(property)) return content;
1649
+ const legacyProperty = new RegExp(`^\\s*${identifier},\\s*$`, "m");
1650
+ if (legacyProperty.test(registryMatch[1])) {
1651
+ return content.replace(legacyProperty, property);
1652
+ }
1653
+ const nextRegistry2 = registryMatch[0].replace(
1654
+ /\n\};$/,
1655
+ `
1656
+ ${property}
1657
+ };`
1658
+ );
1659
+ return content.replace(registryMatch[0], nextRegistry2);
1660
+ }
1411
1661
  const importStatement = `import ${identifier} from "${importPath}";
1412
1662
  `;
1413
- const nextRegistry = registryMatch[0].replace(
1414
- /\n\};$/,
1415
- `
1416
- ${identifier},
1417
- };`
1418
- );
1663
+ const nextRegistry = registryMatch[0].replace(/\n\};$/, `
1664
+ ${property}
1665
+ };`);
1419
1666
  return content.slice(0, declarationIndex) + importStatement + content.slice(declarationIndex).replace(registryMatch[0], nextRegistry);
1420
1667
  }
1421
- function selectedFlags(args) {
1668
+ function selectedFlags(optionArguments) {
1422
1669
  const selected = /* @__PURE__ */ new Set();
1423
- const optionArguments = args.slice(2).filter((argument) => argument.startsWith("-"));
1424
1670
  if (optionArguments.length === 0)
1425
1671
  return /* @__PURE__ */ new Set(["layout", "page", "loading"]);
1426
1672
  for (const argument of optionArguments) {
@@ -1446,23 +1692,61 @@ function selectedFlags(args) {
1446
1692
  }
1447
1693
  return selected;
1448
1694
  }
1695
+ function parseAddPageArguments(args) {
1696
+ const parsedArea = parseAreaOption(args);
1697
+ let logicalName;
1698
+ const optionArguments = [];
1699
+ for (const argument of parsedArea.args.slice(1)) {
1700
+ if (argument.startsWith("-")) {
1701
+ optionArguments.push(argument);
1702
+ continue;
1703
+ }
1704
+ if (logicalName) {
1705
+ throw new CliError(`Unexpected addpage argument: ${argument}.`, {
1706
+ code: "INVALID_ARGUMENT"
1707
+ });
1708
+ }
1709
+ logicalName = argument;
1710
+ }
1711
+ return {
1712
+ area: parsedArea.area,
1713
+ logicalName,
1714
+ flags: selectedFlags(optionArguments)
1715
+ };
1716
+ }
1449
1717
  var addPage = async (args, context) => {
1450
- let logicalName = args[1];
1451
- if (!logicalName || logicalName.startsWith("-")) {
1718
+ const parsed = parseAddPageArguments(args);
1719
+ let { area, logicalName } = parsed;
1720
+ if (!logicalName) {
1452
1721
  if (context.outputMode === "json") {
1453
1722
  throw new CliError("Page name is required in JSON mode.", {
1454
1723
  code: "INTERACTIVE_INPUT_REQUIRED",
1455
1724
  hint: "Pass a simple or Parent.Child page name after addpage."
1456
1725
  });
1457
1726
  }
1458
- const response = await context.prompt({
1459
- type: "text",
1460
- name: "pageName",
1461
- message: "Page name to add:",
1462
- validate: (name) => name ? true : "Page name is required"
1463
- });
1727
+ const questions = [
1728
+ {
1729
+ type: "text",
1730
+ name: "pageName",
1731
+ message: "Page name to add:",
1732
+ validate: (name) => name ? true : "Page name is required"
1733
+ },
1734
+ ...!area ? [
1735
+ {
1736
+ type: "select",
1737
+ name: "area",
1738
+ message: "Page area:",
1739
+ choices: [
1740
+ { title: "public", value: "public" },
1741
+ { title: "user", value: "user" }
1742
+ ]
1743
+ }
1744
+ ] : []
1745
+ ];
1746
+ const response = await context.prompt(questions);
1464
1747
  logicalName = String(response.pageName ?? "");
1465
- if (!logicalName) {
1748
+ area = area ?? response.area;
1749
+ if (!logicalName || !area) {
1466
1750
  context.operations.record({
1467
1751
  action: "cancelled",
1468
1752
  resource: "command",
@@ -1477,14 +1761,17 @@ var addPage = async (args, context) => {
1477
1761
  status: "cancelled"
1478
1762
  });
1479
1763
  }
1764
+ } else {
1765
+ area = requirePageArea(area, "addpage");
1480
1766
  }
1767
+ area = requirePageArea(area, "addpage");
1481
1768
  const pageSegments = parseLogicalName(logicalName, "page name");
1482
1769
  if (pageSegments.length > 2) {
1483
1770
  throw new CliError("Nested pages support exactly Parent.Child.", {
1484
1771
  code: "INVALID_ARGUMENT"
1485
1772
  });
1486
1773
  }
1487
- const flags = selectedFlags(args);
1774
+ const flags = parsed.flags;
1488
1775
  const config = await loadConfig(context);
1489
1776
  if (!config) {
1490
1777
  throw new CliError("Configuration file cnp.config.json was not found.", {
@@ -1507,16 +1794,64 @@ var addPage = async (args, context) => {
1507
1794
  path: useI18n ? "src/app/[locale]" : "src/app"
1508
1795
  });
1509
1796
  }
1797
+ const areaRoot = join6(appRoot, areaRouteGroup(area));
1798
+ if (!context.fs.exists(areaRoot)) {
1799
+ throw new CliError(`The ${area} page area was not found.`, {
1800
+ code: "CONFIG_NOT_FOUND",
1801
+ scope: "project",
1802
+ path: join6(
1803
+ useI18n ? "src/app/[locale]" : "src/app",
1804
+ areaRouteGroup(area)
1805
+ )
1806
+ });
1807
+ }
1510
1808
  const [parentName, childName] = pageSegments.length === 2 ? pageSegments : [void 0, void 0];
1511
1809
  const leafName = childName ?? pageSegments[0];
1512
1810
  const pageIdentifier = capitalize(toIdentifier(leafName));
1513
1811
  const jsonFileName = parentName ?? leafName;
1514
1812
  const uiDirectory = join6(context.cwd, "src", "ui", ...pageSegments);
1515
- const routeDirectory = join6(appRoot, ...pageSegments);
1813
+ const routeDirectory = join6(areaRoot, ...pageSegments);
1814
+ const catalog = await discoverPageCatalog(context.cwd, context.fs);
1815
+ assertConsistentLogicalRoute(catalog, logicalName);
1816
+ const existingOtherArea = catalog.candidates.find(
1817
+ (candidate) => candidate.logicalName === logicalName && candidate.area !== area
1818
+ );
1819
+ const otherArea = area === "public" ? "user" : "public";
1820
+ const otherAreaDirectory = join6(
1821
+ appRoot,
1822
+ areaRouteGroup(otherArea),
1823
+ ...pageSegments
1824
+ );
1825
+ if (existingOtherArea || context.fs.exists(otherAreaDirectory)) {
1826
+ throw new CliError(
1827
+ `Page "${logicalName}" already belongs to the ${otherArea} area.`,
1828
+ {
1829
+ code: "TARGET_EXISTS",
1830
+ scope: "project",
1831
+ path: join6(
1832
+ useI18n ? "src/app/[locale]" : "src/app",
1833
+ areaRouteGroup(otherArea),
1834
+ ...pageSegments
1835
+ )
1836
+ }
1837
+ );
1838
+ }
1839
+ const legacyDirectory = join6(appRoot, ...pageSegments);
1840
+ if (context.fs.exists(legacyDirectory)) {
1841
+ throw new CliError(`Page route "${logicalName}" is ungrouped.`, {
1842
+ code: "INCONSISTENT_ROUTE",
1843
+ scope: "project",
1844
+ path: join6(useI18n ? "src/app/[locale]" : "src/app", ...pageSegments),
1845
+ hint: "Move the route into exactly one of the (public) or (user) areas before retrying."
1846
+ });
1847
+ }
1516
1848
  await assertSafeTarget(context.cwd, uiDirectory, context.fs);
1517
1849
  await assertSafeTarget(context.cwd, routeDirectory, context.fs);
1518
1850
  const templateRoot = join6(context.packageRoot, "templates", "Page");
1519
- const uiTemplate = join6(templateRoot, "page-ui.tsx");
1851
+ const uiTemplate = join6(
1852
+ templateRoot,
1853
+ area === "user" ? "page-ui.user.tsx" : "page-ui.tsx"
1854
+ );
1520
1855
  const routeTemplates = [...flags].map((flag) => ({
1521
1856
  flag,
1522
1857
  source: join6(templateRoot, toFileName(flag))
@@ -1551,6 +1886,7 @@ var addPage = async (args, context) => {
1551
1886
  path: "messages"
1552
1887
  });
1553
1888
  }
1889
+ await assertSafeTarget(context.cwd, messagesRoot, context.fs);
1554
1890
  const locales = (await context.fs.list(messagesRoot)).filter((entry) => entry.isDirectory).map((entry) => entry.name).sort();
1555
1891
  if (locales.length === 0) {
1556
1892
  throw new CliError("No locale directories were found.", {
@@ -1562,6 +1898,8 @@ var addPage = async (args, context) => {
1562
1898
  for (const locale of locales) {
1563
1899
  const target = join6(messagesRoot, locale, `${jsonFileName}.json`);
1564
1900
  const aggregator = join6(messagesRoot, `${locale}.ts`);
1901
+ await assertSafeTarget(context.cwd, target, context.fs);
1902
+ await assertSafeTarget(context.cwd, aggregator, context.fs);
1565
1903
  if (!context.fs.exists(aggregator)) {
1566
1904
  throw new CliError(
1567
1905
  `Locale aggregator messages/${locale}.ts was not found.`,
@@ -1612,7 +1950,8 @@ var addPage = async (args, context) => {
1612
1950
  const gateway = new MutationGateway(context, context.cwd);
1613
1951
  await gateway.mkdir(uiDirectory, {
1614
1952
  role: "page-ui-directory",
1615
- resource: "directory"
1953
+ resource: "directory",
1954
+ detail: { area }
1616
1955
  });
1617
1956
  const uiFile = join6(uiDirectory, "page-ui.tsx");
1618
1957
  const uiContent = (await context.fs.readText(uiTemplate)).replace(
@@ -1621,11 +1960,13 @@ var addPage = async (args, context) => {
1621
1960
  ).replaceAll('from "@/', `from "${aliasPrefix}/`).replace(/template/g, pageIdentifier).replace(/Template/g, pageIdentifier);
1622
1961
  await gateway.write(uiFile, uiContent, {
1623
1962
  role: "page-ui",
1624
- preserveExisting: true
1963
+ preserveExisting: true,
1964
+ detail: { area }
1625
1965
  });
1626
1966
  await gateway.mkdir(routeDirectory, {
1627
1967
  role: "page-route-directory",
1628
- resource: "directory"
1968
+ resource: "directory",
1969
+ detail: { area }
1629
1970
  });
1630
1971
  for (const template of routeTemplates) {
1631
1972
  const filename = toFileName(template.flag);
@@ -1634,7 +1975,8 @@ var addPage = async (args, context) => {
1634
1975
  const content = (await context.fs.readText(template.source)).replace("@/ui/template/", `@/ui/${uiImportPath}/`).replaceAll('from "@/', `from "${aliasPrefix}/`).replace(/template/g, pageIdentifier).replace(/Template/g, pageIdentifier);
1635
1976
  await gateway.write(target, content, {
1636
1977
  role: `page-${template.flag}`,
1637
- preserveExisting: true
1978
+ preserveExisting: true,
1979
+ detail: { area }
1638
1980
  });
1639
1981
  }
1640
1982
  if (useI18n) {
@@ -1642,23 +1984,32 @@ var addPage = async (args, context) => {
1642
1984
  if (item.messageExists) {
1643
1985
  gateway.unchanged(item.target, {
1644
1986
  role: "translation-messages",
1645
- detail: { locale: item.locale, namespace: translationNamespace }
1987
+ detail: {
1988
+ area,
1989
+ locale: item.locale,
1990
+ namespace: translationNamespace
1991
+ }
1646
1992
  });
1647
1993
  } else {
1648
1994
  await gateway.write(item.target, item.targetContent, {
1649
1995
  role: "translation-messages",
1650
- detail: { locale: item.locale, namespace: translationNamespace }
1996
+ detail: {
1997
+ area,
1998
+ locale: item.locale,
1999
+ namespace: translationNamespace
2000
+ }
1651
2001
  });
1652
2002
  }
1653
2003
  await gateway.write(item.aggregator, item.aggregatorContent, {
1654
- role: "locale-aggregator"
2004
+ role: "locale-aggregator",
2005
+ detail: { area }
1655
2006
  });
1656
2007
  }
1657
2008
  } else {
1658
2009
  gateway.skipped(join6(context.cwd, "messages"), {
1659
2010
  role: "translation-messages",
1660
2011
  resource: "directory",
1661
- detail: { reason: "Internationalization is disabled." }
2012
+ detail: { area, reason: "Internationalization is disabled." }
1662
2013
  });
1663
2014
  }
1664
2015
  const mutated = context.operations.snapshot().some(
@@ -1677,8 +2028,9 @@ var addPage = async (args, context) => {
1677
2028
  ];
1678
2029
  return commandResult(context, {
1679
2030
  command: "addpage",
1680
- summary: mutated ? `Added page "${logicalName}" and its missing resources.` : `Page "${logicalName}" already exists and was preserved.`,
2031
+ summary: mutated ? `Added ${area} page "${logicalName}" and its missing resources.` : `${area[0].toUpperCase()}${area.slice(1)} page "${logicalName}" already exists and was preserved.`,
1681
2032
  projectRoot: context.cwd,
2033
+ data: { area, logicalName },
1682
2034
  nextSteps: mutated ? [
1683
2035
  {
1684
2036
  kind: "review",
@@ -1841,13 +2193,15 @@ function escapeRegExp(value) {
1841
2193
  function unregisterMessagesFile(content, locale, fileName) {
1842
2194
  const identifier = toIdentifier(fileName);
1843
2195
  const escapedIdentifier = escapeRegExp(identifier);
2196
+ const escapedFileName = escapeRegExp(fileName);
1844
2197
  const escapedPath = escapeRegExp(`./${locale}/${fileName}.json`);
1845
2198
  const importPattern = new RegExp(
1846
2199
  `^import\\s+${escapedIdentifier}\\s+from\\s+["']${escapedPath}["'];?\\r?\\n?`,
1847
2200
  "m"
1848
2201
  );
2202
+ const propertyExpression = identifier === fileName ? escapedIdentifier : `(?:["']${escapedFileName}["']\\s*:\\s*)?${escapedIdentifier}`;
1849
2203
  const propertyPattern = new RegExp(
1850
- `^\\s*${escapedIdentifier},\\s*\\r?\\n?`,
2204
+ `^\\s*${propertyExpression},\\s*\\r?\\n?`,
1851
2205
  "m"
1852
2206
  );
1853
2207
  const hasImport = importPattern.test(content);
@@ -1868,24 +2222,63 @@ function unregisterMessagesFile(content, locale, fileName) {
1868
2222
  changed: true
1869
2223
  };
1870
2224
  }
2225
+ function parseRmPageArguments(args) {
2226
+ const parsedArea = parseAreaOption(args);
2227
+ let logicalName;
2228
+ for (const argument of parsedArea.args.slice(1)) {
2229
+ if (argument.startsWith("-")) {
2230
+ throw new CliError(`Unknown rmpage option: ${argument}.`, {
2231
+ code: "INVALID_ARGUMENT"
2232
+ });
2233
+ }
2234
+ if (logicalName) {
2235
+ throw new CliError(`Unexpected rmpage argument: ${argument}.`, {
2236
+ code: "INVALID_ARGUMENT"
2237
+ });
2238
+ }
2239
+ logicalName = argument;
2240
+ }
2241
+ return { area: parsedArea.area, logicalName };
2242
+ }
1871
2243
  var rmPage = async (args, context) => {
1872
- const candidates = await discoverPages(context.cwd, context.fs);
1873
- let logicalName = args[1];
1874
- if (!logicalName || logicalName.startsWith("-")) {
2244
+ const parsed = parseRmPageArguments(args);
2245
+ const catalog = await discoverPageCatalog(context.cwd, context.fs);
2246
+ let { area, logicalName } = parsed;
2247
+ let candidate;
2248
+ if (!logicalName) {
1875
2249
  if (context.outputMode === "json") {
1876
2250
  throw new CliError("Page name is required in JSON mode.", {
1877
2251
  code: "INTERACTIVE_INPUT_REQUIRED",
1878
2252
  hint: "Pass a page name returned by completion after rmpage."
1879
2253
  });
1880
2254
  }
2255
+ const candidates = area ? catalog.candidates.filter((entry) => entry.area === area) : catalog.candidates;
2256
+ if (candidates.length === 0) {
2257
+ const issue = catalog.issues[0];
2258
+ if (issue) {
2259
+ throw new CliError(
2260
+ `Page route "${issue.logicalName}" is inconsistent.`,
2261
+ {
2262
+ code: "INCONSISTENT_ROUTE",
2263
+ scope: "project",
2264
+ path: issue.routeDirectories.join(", "),
2265
+ hint: "Move the route into exactly one of the (public) or (user) areas before retrying."
2266
+ }
2267
+ );
2268
+ }
2269
+ throw new CliError(
2270
+ area ? `No pages were found in the ${area} area.` : "No removable pages were found.",
2271
+ { code: "TARGET_NOT_FOUND", scope: "project", path: "." }
2272
+ );
2273
+ }
1881
2274
  const selected = await context.prompt([
1882
2275
  {
1883
2276
  type: "autocomplete",
1884
2277
  name: "page",
1885
2278
  message: "Page to remove:",
1886
2279
  choices: candidates.map((candidate2) => ({
1887
- title: candidate2.logicalName.replaceAll(".", " > "),
1888
- value: candidate2.logicalName
2280
+ title: `${candidate2.area[0].toUpperCase()}${candidate2.area.slice(1)} > ${candidate2.logicalName.replaceAll(".", " > ")}`,
2281
+ value: candidate2.id
1889
2282
  }))
1890
2283
  },
1891
2284
  {
@@ -1895,38 +2288,51 @@ var rmPage = async (args, context) => {
1895
2288
  initial: false
1896
2289
  }
1897
2290
  ]);
2291
+ const selectedId = String(selected.page ?? "");
2292
+ const selectedCandidate = candidates.find(
2293
+ (entry) => entry.id === selectedId
2294
+ );
1898
2295
  if (!selected.confirm) {
1899
2296
  context.operations.record({
1900
2297
  action: "cancelled",
1901
2298
  resource: "command",
1902
2299
  role: "page-removal",
1903
2300
  scope: "project",
1904
- path: "."
2301
+ path: ".",
2302
+ detail: selectedCandidate ? { area: selectedCandidate.area } : area ? { area } : void 0
1905
2303
  });
1906
2304
  return commandResult(context, {
1907
2305
  command: "rmpage",
1908
- summary: "Page deletion was cancelled.",
2306
+ summary: selectedCandidate ? `Deletion of ${selectedCandidate.area} page "${selectedCandidate.logicalName}" was cancelled.` : area ? `Page deletion in the ${area} area was cancelled.` : "Page deletion was cancelled.",
1909
2307
  projectRoot: context.cwd,
1910
- status: "cancelled"
2308
+ status: "cancelled",
2309
+ data: selectedCandidate ? {
2310
+ area: selectedCandidate.area,
2311
+ logicalName: selectedCandidate.logicalName
2312
+ } : area ? { area } : void 0
1911
2313
  });
1912
2314
  }
1913
- logicalName = String(selected.page ?? "");
1914
- }
1915
- parseLogicalName(logicalName, "page name");
1916
- const candidate = candidates.find(
1917
- (entry) => entry.logicalName === logicalName
1918
- );
1919
- if (!candidate) {
1920
- throw new CliError(`Page not found: ${logicalName}.`, {
1921
- code: "TARGET_NOT_FOUND",
1922
- scope: "project",
1923
- path: logicalName.replaceAll(".", "/")
1924
- });
2315
+ if (!selectedCandidate) {
2316
+ throw new CliError("The selected page is not in the page catalog.", {
2317
+ code: "INVALID_ARGUMENT"
2318
+ });
2319
+ }
2320
+ candidate = selectedCandidate;
2321
+ logicalName = candidate.logicalName;
2322
+ area = candidate.area;
2323
+ } else {
2324
+ area = requirePageArea(area, "rmpage");
2325
+ parseLogicalName(logicalName, "page name");
2326
+ candidate = resolvePageCandidate(catalog, logicalName, area);
1925
2327
  }
2328
+ area = requirePageArea(area, "rmpage");
1926
2329
  const messagesRoot = resolveInside(context.cwd, "messages");
2330
+ await assertSafeTarget(context.cwd, candidate.uiDirectory, context.fs);
2331
+ await assertSafeTarget(context.cwd, candidate.routeDirectory, context.fs);
1927
2332
  const preparedMessages = [];
1928
2333
  const preparedAggregators = [];
1929
2334
  if (context.fs.exists(messagesRoot)) {
2335
+ await assertSafeTarget(context.cwd, messagesRoot, context.fs);
1930
2336
  const locales = (await context.fs.list(messagesRoot)).filter((entry) => entry.isDirectory).map((entry) => entry.name).sort();
1931
2337
  for (const locale of locales) {
1932
2338
  const target = resolveInside(
@@ -1935,12 +2341,14 @@ var rmPage = async (args, context) => {
1935
2341
  locale,
1936
2342
  `${candidate.routeSegments[0]}.json`
1937
2343
  );
2344
+ await assertSafeTarget(context.cwd, target, context.fs);
1938
2345
  if (!candidate.messageKey) {
1939
2346
  const aggregator = resolveInside(
1940
2347
  context.cwd,
1941
2348
  "messages",
1942
2349
  `${locale}.ts`
1943
2350
  );
2351
+ await assertSafeTarget(context.cwd, aggregator, context.fs);
1944
2352
  if (!context.fs.exists(aggregator)) {
1945
2353
  throw new CliError(
1946
2354
  `Locale aggregator messages/${locale}.ts was not found.`,
@@ -1993,33 +2401,41 @@ var rmPage = async (args, context) => {
1993
2401
  const gateway = new MutationGateway(context, context.cwd);
1994
2402
  for (const prepared of preparedMessages) {
1995
2403
  if (!candidate.messageKey) {
1996
- await gateway.remove(prepared.target, { role: "translation-messages" });
2404
+ await gateway.remove(prepared.target, {
2405
+ role: "translation-messages",
2406
+ detail: { area }
2407
+ });
1997
2408
  } else if (prepared.keyPresent) {
1998
2409
  await gateway.write(prepared.target, prepared.content, {
1999
2410
  role: "translation-messages",
2000
- detail: { removedKey: candidate.messageKey }
2411
+ detail: { area, removedKey: candidate.messageKey }
2001
2412
  });
2002
2413
  } else {
2003
2414
  gateway.unchanged(prepared.target, {
2004
2415
  role: "translation-messages",
2005
- detail: { missingKey: candidate.messageKey }
2416
+ detail: { area, missingKey: candidate.messageKey }
2006
2417
  });
2007
2418
  }
2008
2419
  }
2009
2420
  for (const prepared of preparedAggregators) {
2010
2421
  if (prepared.changed) {
2011
2422
  await gateway.write(prepared.target, prepared.content, {
2012
- role: "locale-aggregator"
2423
+ role: "locale-aggregator",
2424
+ detail: { area }
2013
2425
  });
2014
2426
  } else {
2015
- gateway.unchanged(prepared.target, { role: "locale-aggregator" });
2427
+ gateway.unchanged(prepared.target, {
2428
+ role: "locale-aggregator",
2429
+ detail: { area }
2430
+ });
2016
2431
  }
2017
2432
  }
2018
2433
  await gateway.remove(
2019
2434
  candidate.uiDirectory,
2020
2435
  {
2021
2436
  role: "page-ui",
2022
- resource: "directory"
2437
+ resource: "directory",
2438
+ detail: { area }
2023
2439
  },
2024
2440
  { recursive: true, force: false }
2025
2441
  );
@@ -2027,15 +2443,17 @@ var rmPage = async (args, context) => {
2027
2443
  candidate.routeDirectory,
2028
2444
  {
2029
2445
  role: "page-route",
2030
- resource: "directory"
2446
+ resource: "directory",
2447
+ detail: { area }
2031
2448
  },
2032
2449
  { recursive: true, force: false }
2033
2450
  );
2034
2451
  const mutated = context.operations.snapshot().some((event) => event.action === "deleted" || event.action === "updated");
2035
2452
  return commandResult(context, {
2036
2453
  command: "rmpage",
2037
- summary: mutated ? `Deleted page "${logicalName}" and its associated resources.` : `No resources remained for page "${logicalName}".`,
2454
+ summary: mutated ? `Deleted ${area} page "${logicalName}" and its associated resources.` : `No resources remained for ${area} page "${logicalName}".`,
2038
2455
  projectRoot: context.cwd,
2456
+ data: { area, logicalName },
2039
2457
  nextSteps: mutated ? [
2040
2458
  {
2041
2459
  kind: "run-checks",
@@ -2067,6 +2485,24 @@ import { fileURLToPath } from "url";
2067
2485
 
2068
2486
  // src/core/template-manifest.ts
2069
2487
  import path8 from "path";
2488
+
2489
+ // src/core/agent-guidance.ts
2490
+ var PROJECT_SKILL_NAMES = [
2491
+ "create-next-pro-create-project",
2492
+ "create-next-pro-addcomponent",
2493
+ "create-next-pro-addpage",
2494
+ "create-next-pro-addlib",
2495
+ "create-next-pro-addapi",
2496
+ "create-next-pro-addlanguage",
2497
+ "create-next-pro-addtext",
2498
+ "create-next-pro-rmpage"
2499
+ ];
2500
+ var PROJECT_AGENT_GUIDANCE_FILES = [
2501
+ "AGENTS.md",
2502
+ ...PROJECT_SKILL_NAMES.map((name) => `.agents/skills/${name}/SKILL.md`)
2503
+ ];
2504
+
2505
+ // src/core/template-manifest.ts
2070
2506
  var TEMPLATE_DENY_NAMES = /* @__PURE__ */ new Set([
2071
2507
  ".env",
2072
2508
  ".git",
@@ -2116,6 +2552,9 @@ async function templateManifest(root, fs) {
2116
2552
  async function validateScaffoldTemplate(root, fs) {
2117
2553
  const manifest = await templateManifest(root, fs);
2118
2554
  for (const required of [
2555
+ ...PROJECT_AGENT_GUIDANCE_FILES.map(
2556
+ (relative) => relative.split("/").join(path8.sep)
2557
+ ),
2119
2558
  "package.json",
2120
2559
  "tsconfig.json",
2121
2560
  ".env.example",
@@ -2124,6 +2563,7 @@ async function validateScaffoldTemplate(root, fs) {
2124
2563
  "bun.lock",
2125
2564
  "pnpm-workspace.yaml",
2126
2565
  "vitest.config.ts",
2566
+ path8.join("scripts", "audit-policy.ts"),
2127
2567
  path8.join("scripts", "audit.ts"),
2128
2568
  path8.join("scripts", "package-manager.ts"),
2129
2569
  path8.join("tests", "consumer", "validate-template.ts"),
@@ -2560,18 +3000,19 @@ var HELP_TEXT = `create-next-pro
2560
3000
 
2561
3001
  Usage:
2562
3002
  create-next-pro <project-name> [--force] [--json]
2563
- create-next-pro addpage [options] [--json]
2564
- create-next-pro addcomponent [options] [--json]
3003
+ create-next-pro addpage [name] --area <public|user> [options] [--json]
3004
+ create-next-pro addcomponent [name] [--page <page> --area <public|user>] [--json]
2565
3005
  create-next-pro addlib [name] [--json]
2566
3006
  create-next-pro addapi [name] [--json]
2567
3007
  create-next-pro addlanguage [locale] [--json]
2568
3008
  create-next-pro addtext <path> [text] [--json]
2569
- create-next-pro rmpage [options] [--json]
3009
+ create-next-pro rmpage [name] [--area <public|user>] [--json]
2570
3010
 
2571
3011
  Options:
2572
3012
  --help Show this help message
2573
3013
  --version Show the CLI version
2574
3014
  --json Emit one deterministic JSON document
3015
+ --area Select the public or user area for page operations
2575
3016
  --reconfigure Run the configuration assistant again
2576
3017
  `;
2577
3018
  function parseOptions(args) {
@@ -2659,7 +3100,10 @@ async function executeCli(context) {
2659
3100
  }
2660
3101
  async function runCompletion(context, args) {
2661
3102
  try {
2662
- for (const candidate of await completionCandidates(args[1], context)) {
3103
+ for (const candidate of await completionCandidates(
3104
+ args.slice(1),
3105
+ context
3106
+ )) {
2663
3107
  context.terminal.log(candidate);
2664
3108
  }
2665
3109
  return 0;