create-pracht 0.6.2 → 0.7.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.
package/src/index.js CHANGED
@@ -109,6 +109,31 @@ const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
109
109
  // scripts/sync-skills.js); inside the monorepo we fall back to the source.
110
110
  const SKILL_DIRS = [resolve(PACKAGE_ROOT, "skills"), resolve(PACKAGE_ROOT, "../../skills")];
111
111
 
112
+ /**
113
+ * The skills a brand-new app can actually use, seeded by default.
114
+ *
115
+ * The full catalog is ~33 SKILL.md files, about 360 KB, against a starter with
116
+ * eight source files. Every `description` is in the agent's system prompt for
117
+ * every session whether the skill runs or not, so seeding all of them spends
118
+ * the app's context budget on audits that have nothing to audit yet and on
119
+ * additive scaffolds for decisions the author has not made.
120
+ *
121
+ * These five cover the lifecycle of the app as scaffolded — write it, fix it,
122
+ * ship it, keep it current — plus the one thing pracht does that an agent will
123
+ * not infer from any other framework it knows. Everything else is one
124
+ * `pracht skills add <name>` away, and the generated README and AGENTS.md say
125
+ * so.
126
+ */
127
+ const CORE_SKILLS = [
128
+ "pracht-scaffold",
129
+ "pracht-debug",
130
+ "pracht-deploy",
131
+ "upgrade-pracht",
132
+ "add-capabilities",
133
+ ];
134
+
135
+ const SKILL_CATALOG_URL = "https://pracht.resynapse.dev/.well-known/agent-skills/index.json";
136
+
112
137
  export async function run(argv = process.argv.slice(2)) {
113
138
  const options = parseArgs(argv);
114
139
  const packageManagerUserAgent = process.env.npm_config_user_agent ?? "";
@@ -125,6 +150,7 @@ export async function run(argv = process.argv.slice(2)) {
125
150
  const router = options.router ?? (options.yes ? "manifest" : null);
126
151
  const tailwind = options.tailwind ?? (options.yes ? false : null);
127
152
  const agentTools = options.agentTools ?? (options.yes ? true : null);
153
+ const agentSkills = options.agentSkills ?? "core";
128
154
 
129
155
  let resolvedDir = dir;
130
156
  let resolvedAdapter = adapterId;
@@ -162,6 +188,7 @@ export async function run(argv = process.argv.slice(2)) {
162
188
  if (options.dryRun) {
163
189
  const { files } = await buildProjectFiles({
164
190
  adapter: ADAPTERS[resolvedAdapter],
191
+ agentSkills,
165
192
  agentTools: resolvedAgentTools,
166
193
  packageManager,
167
194
  pnpmMajor,
@@ -178,6 +205,7 @@ export async function run(argv = process.argv.slice(2)) {
178
205
  console.log(
179
206
  JSON.stringify({
180
207
  adapter: resolvedAdapter,
208
+ agentSkills: resolvedAgentTools ? agentSkills : null,
181
209
  agentTools: resolvedAgentTools,
182
210
  directory: resolvedDir,
183
211
  dryRun: true,
@@ -199,6 +227,7 @@ export async function run(argv = process.argv.slice(2)) {
199
227
 
200
228
  const { pnpmWorkspaceNotice } = await scaffoldProject({
201
229
  adapter: ADAPTERS[resolvedAdapter],
230
+ agentSkills,
202
231
  agentTools: resolvedAgentTools,
203
232
  packageManager,
204
233
  pnpmMajor,
@@ -234,6 +263,7 @@ export async function run(argv = process.argv.slice(2)) {
234
263
  if (options.json) {
235
264
  const { files } = await buildProjectFiles({
236
265
  adapter: ADAPTERS[resolvedAdapter],
266
+ agentSkills,
237
267
  agentTools: resolvedAgentTools,
238
268
  packageManager,
239
269
  pnpmMajor,
@@ -247,6 +277,7 @@ export async function run(argv = process.argv.slice(2)) {
247
277
  console.log(
248
278
  JSON.stringify({
249
279
  adapter: resolvedAdapter,
280
+ agentSkills: resolvedAgentTools ? agentSkills : null,
250
281
  agentTools: resolvedAgentTools,
251
282
  directory: resolvedDir,
252
283
  files: Object.keys(files).sort(),
@@ -263,6 +294,7 @@ export async function run(argv = process.argv.slice(2)) {
263
294
  } else {
264
295
  printNextSteps({
265
296
  adapter: ADAPTERS[resolvedAdapter],
297
+ agentSkills,
266
298
  agentTools: resolvedAgentTools,
267
299
  dir: resolvedDir,
268
300
  installSucceeded,
@@ -277,6 +309,7 @@ export async function run(argv = process.argv.slice(2)) {
277
309
 
278
310
  export async function scaffoldProject({
279
311
  adapter,
312
+ agentSkills = "core",
280
313
  agentTools = true,
281
314
  packageManager,
282
315
  pnpmMajor = 11,
@@ -288,6 +321,7 @@ export async function scaffoldProject({
288
321
  const packageName = toPackageName(basename(targetDir));
289
322
  const { files, pnpmWorkspaceNotice } = await buildProjectFiles({
290
323
  adapter,
324
+ agentSkills,
291
325
  agentTools,
292
326
  packageManager,
293
327
  pnpmMajor,
@@ -343,6 +377,7 @@ export function getPnpmMajor(userAgent = process.env.npm_config_user_agent ?? ""
343
377
  export function parseArgs(argv) {
344
378
  const options = {
345
379
  adapter: undefined,
380
+ agentSkills: undefined,
346
381
  agentTools: undefined,
347
382
  dir: undefined,
348
383
  dryRun: false,
@@ -380,6 +415,16 @@ export function parseArgs(argv) {
380
415
  continue;
381
416
  }
382
417
 
418
+ if (arg.startsWith("--agent-tools=")) {
419
+ const value = arg.slice("--agent-tools=".length).toLowerCase();
420
+ if (value !== "core" && value !== "full") {
421
+ throw new ValidationError(`Invalid --agent-tools value: ${value}. Use core or full.`);
422
+ }
423
+ options.agentTools = true;
424
+ options.agentSkills = value;
425
+ continue;
426
+ }
427
+
383
428
  if (arg === "--no-agent-tools") {
384
429
  options.agentTools = false;
385
430
  continue;
@@ -488,10 +533,11 @@ async function promptForRouter(readline) {
488
533
  // The two routers are not equivalent, and the difference is invisible until
489
534
  // you reach for a manifest-only feature. Say so at the point of choosing.
490
535
  console.log("Router:");
491
- console.log(" 1. Manifest (explicit routes.ts) — supports middleware, capabilities,");
492
- console.log(" MCP, Web Bot Auth, and constraints");
493
- console.log(" 2. Pages (file-system routing) — pages and API routes only; no");
494
- console.log(" middleware, capabilities, MCP, or agent trust (eject later to add them)");
536
+ console.log(" 1. Manifest (explicit routes.ts) — per-route middleware and shells,");
537
+ console.log(" path-prefix groups, explicit route ids");
538
+ console.log(" 2. Pages (file-system routing) — routes, `_app.tsx` shells, one root");
539
+ console.log(" _middleware.ts, src/capabilities/, and _app.config.ts for agents");
540
+ console.log(" and constraints; no per-route middleware or shell overrides");
495
541
 
496
542
  while (true) {
497
543
  const answer = await readline.question("Router (1): ");
@@ -647,6 +693,7 @@ async function resolveVersions(packageNames, { remote = true } = {}) {
647
693
 
648
694
  async function buildProjectFiles({
649
695
  adapter,
696
+ agentSkills = "core",
650
697
  agentTools = true,
651
698
  packageManager,
652
699
  pnpmMajor = 11,
@@ -675,7 +722,8 @@ async function buildProjectFiles({
675
722
 
676
723
  const versions = await resolveVersions(packagesToResolve, { remote: resolveRemoteVersions });
677
724
  const policyMajor = pnpmMajor ?? 11;
678
- const ancestorWorkspace = targetDir ? findAncestorPnpmWorkspace(targetDir) : null;
725
+ const ancestorWorkspace =
726
+ targetDir && packageManager === "pnpm" ? findAncestorPnpmWorkspace(targetDir) : null;
679
727
  const pnpmWorkspaceNotice = ancestorWorkspace
680
728
  ? {
681
729
  packages: pnpmBuildAllowlist(adapter, tailwind),
@@ -689,6 +737,7 @@ async function buildProjectFiles({
689
737
  "dist\nnode_modules\n.netlify\n.wrangler\n.vercel\n.env*\n!.env.example\n.dev.vars\n# Keep .pracht/app-graph.json committed — it is the `pracht plan` snapshot.\n",
690
738
  "README.md": createReadme({
691
739
  adapter,
740
+ agentSkills,
692
741
  agentTools,
693
742
  packageManager,
694
743
  pnpmMajor,
@@ -716,6 +765,7 @@ async function buildProjectFiles({
716
765
  if (agentTools) {
717
766
  files["AGENTS.md"] = createAgentInstructions({
718
767
  adapter,
768
+ agentSkills,
719
769
  agentTools,
720
770
  packageManager,
721
771
  router,
@@ -754,15 +804,19 @@ async function buildProjectFiles({
754
804
 
755
805
  if (agentTools) {
756
806
  files[".mcp.json"] = createMcpConfig();
757
- Object.assign(files, await readSkillFiles());
758
- }
759
-
760
- // pnpm resolves build-script policy from the workspace root, so inside an existing
761
- // workspace our own file would be read by nobody and `pnpm install` run
762
- // from the app directory would find it first and re-root the workspace there,
763
- // detaching the app from its siblings. Decided here so the `--json` and
764
- // `--dry-run` listings match what is actually written.
765
- if (!pnpmWorkspaceNotice) {
807
+ Object.assign(files, await readSkillFiles(agentSkills));
808
+ }
809
+
810
+ // Only pnpm reads this file; npm, yarn, and bun ignore it entirely, so
811
+ // emitting it for them leaves a config in the repo that nothing in the repo
812
+ // obeys and that a reader has to look up to dismiss.
813
+ //
814
+ // pnpm itself resolves build-script policy from the workspace root, so inside
815
+ // an existing workspace our own file would be read by nobody — and `pnpm
816
+ // install` run from the app directory would find it first and re-root the
817
+ // workspace there, detaching the app from its siblings. Decided here so the
818
+ // `--json` and `--dry-run` listings match what is actually written.
819
+ if (packageManager === "pnpm" && !pnpmWorkspaceNotice) {
766
820
  files["pnpm-workspace.yaml"] = createPnpmWorkspaceConfig(adapter, tailwind, policyMajor);
767
821
  }
768
822
 
@@ -781,7 +835,7 @@ function createMcpConfig() {
781
835
  // than the one the app builds with. Not bare `npx pracht` either:
782
836
  // that resolves to a registry package literally named `pracht`
783
837
  // whenever the local bin is missing — `--no-install` fails loudly.
784
- args: ["--no-install", "pracht", "mcp"],
838
+ args: ["--no-install", "pracht", "dev-mcp"],
785
839
  },
786
840
  },
787
841
  },
@@ -790,15 +844,19 @@ function createMcpConfig() {
790
844
  )}\n`;
791
845
  }
792
846
 
793
- async function readSkillFiles() {
847
+ async function readSkillFiles(scope = "core") {
794
848
  const skillsDir = SKILL_DIRS.find((dir) => existsSync(dir));
795
849
 
796
850
  if (!skillsDir) {
797
851
  return {};
798
852
  }
799
853
 
854
+ const wanted = scope === "full" ? null : new Set(CORE_SKILLS);
800
855
  const files = {};
801
856
  for (const name of await readdir(skillsDir)) {
857
+ if (wanted && !wanted.has(name)) {
858
+ continue;
859
+ }
802
860
  const skillFile = resolve(skillsDir, name, "SKILL.md");
803
861
  if (!existsSync(skillFile)) {
804
862
  continue;
@@ -856,18 +914,20 @@ function createPackageJson({ adapter, projectName, tailwind, versions }) {
856
914
  devDependencies.tailwindcss = versions["tailwindcss"];
857
915
  }
858
916
 
917
+ // Conventional key order, not alphabetical: `name` belongs at the top of a
918
+ // package.json, not between devDependencies and private.
859
919
  return `${JSON.stringify(
860
920
  {
921
+ name: projectName,
922
+ version: "0.0.0",
923
+ private: true,
924
+ type: "module",
925
+ scripts,
861
926
  dependencies: {
862
927
  [adapter.packageName]: versions[adapter.packageName],
863
928
  "@pracht/core": versions["@pracht/core"],
864
929
  },
865
930
  devDependencies,
866
- name: projectName,
867
- private: true,
868
- scripts,
869
- type: "module",
870
- version: "0.0.0",
871
931
  },
872
932
  null,
873
933
  2,
@@ -1082,23 +1142,26 @@ function createPagesHomeRoute(adapter) {
1082
1142
  }
1083
1143
 
1084
1144
  function createBaseTSConfig(_adapter) {
1145
+ // Two-space and trailing-newline like every other generated file, and grouped
1146
+ // the way tsconfig documentation presents these options rather than
1147
+ // alphabetically.
1085
1148
  const config = {
1086
1149
  compilerOptions: {
1087
- allowImportingTsExtensions: true,
1088
- jsx: "react-jsx",
1089
- jsxImportSource: "preact",
1150
+ target: "ES2022",
1090
1151
  lib: ["ES2022", "DOM", "DOM.Iterable"],
1091
1152
  module: "ESNext",
1092
1153
  moduleResolution: "Bundler",
1093
- noEmit: true,
1094
- skipLibCheck: true,
1095
- strict: true,
1096
- target: "ES2022",
1097
- types: ["vite/client"],
1154
+ jsx: "react-jsx",
1155
+ jsxImportSource: "preact",
1156
+ types: ["vite/client", "@pracht/vite-plugin/virtual"],
1157
+ allowImportingTsExtensions: true,
1098
1158
  verbatimModuleSyntax: true,
1159
+ strict: true,
1160
+ skipLibCheck: true,
1161
+ noEmit: true,
1099
1162
  },
1100
1163
  };
1101
- return JSON.stringify(config, null, 4);
1164
+ return `${JSON.stringify(config, null, 2)}\n`;
1102
1165
  }
1103
1166
 
1104
1167
  function createHealthRoute(adapter) {
@@ -1426,16 +1489,26 @@ function createDockerignore() {
1426
1489
  }
1427
1490
 
1428
1491
  const PAGES_ROUTER_LIMITATIONS =
1429
- "**The pages router has no manifest**, so these manifest-only features are unavailable: named shells (there is one, `_app.tsx`), route middleware, capabilities (and therefore capability HTTP endpoints, WebMCP, remote MCP, and `pracht eval`), `defineApp({ constraints })`, and `agents`. If the app needs auth policy or a runtime agent surface, eject with `generateRoutesFile` from `@pracht/vite-plugin/pages-router`, remove `pagesDir`, and customize the generated manifest.";
1492
+ "**The pages router has no manifest.** Everything a manifest registers by name is registered by file instead: an `_app.tsx` per directory (the nearest one wraps that subtree), one root `_middleware.ts` applied to every page route, every module in `src/capabilities/`, and `agents` / `constraints` as named exports of `src/pages/_app.config.ts`. A `pages/404.tsx` file supplies the not-found page. Capability HTTP endpoints, WebMCP, remote MCP, typed clients, and `pracht eval` all work. What still needs a manifest: per-route middleware assignment, per-route shell overrides, `group({ pathPrefix })`, explicit route ids, and webhook ISG policies. Pure static exports have no request runtime, so `_middleware.ts` and capability endpoints do not apply there. To move over, eject with `generateRoutesFile` from `@pracht/vite-plugin/pages-router`, remove `pagesDir`, and customize the generated manifest.";
1430
1493
 
1431
1494
  const PAGES_ROUTER_ISG_POLICY =
1432
1495
  'Pages-router ISG supports time revalidation only: pair `export const RENDER_MODE = "isg"` with a positive integer such as `export const REVALIDATE = 3600`. Missing or misplaced policies fail `pracht build`, `doctor`, and `verify`. Webhook revalidation and combined policies require an explicit manifest.';
1433
1496
 
1434
- function createAgentInstructions({ adapter, agentTools, packageManager, router, tailwind }) {
1497
+ function createAgentInstructions({
1498
+ adapter,
1499
+ agentSkills = "core",
1500
+ agentTools,
1501
+ packageManager,
1502
+ router,
1503
+ tailwind,
1504
+ }) {
1435
1505
  // `bun build` is Bun's own bundler and shadows the package script, so bun
1436
1506
  // needs the explicit `run` form the same way npm does.
1437
1507
  const runCmd =
1438
1508
  packageManager === "npm" || packageManager === "bun" ? `${packageManager} run` : packageManager;
1509
+ // `pnpm deploy` is pnpm's own workspace-deploy command and shadows the
1510
+ // package script, so this one script needs `run` for pnpm too.
1511
+ const deployCmd = packageManager === "pnpm" ? "pnpm run" : runCmd;
1439
1512
  // The pages router derives route ids from filenames, so the home page of a
1440
1513
  // pages app is `index` (`src/pages/index.tsx`); the manifest scaffold names
1441
1514
  // it `home` explicitly. Every id in the instructions below has to be one the
@@ -1466,7 +1539,7 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1466
1539
  }
1467
1540
 
1468
1541
  if (adapter.id === "cloudflare" || adapter.id === "netlify" || adapter.id === "vercel") {
1469
- lines.push(`- \`${runCmd} deploy\` — build and deploy`);
1542
+ lines.push(`- \`${deployCmd} deploy\` — build and deploy`);
1470
1543
  }
1471
1544
 
1472
1545
  lines.push("");
@@ -1480,6 +1553,10 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1480
1553
  if (adapter.id !== "static") {
1481
1554
  lines.push("- `pracht generate middleware --name auth` — add middleware");
1482
1555
  }
1556
+ } else if (adapter.id !== "static") {
1557
+ lines.push(
1558
+ "- `pracht generate middleware --name _middleware` — add root middleware for every page route",
1559
+ );
1483
1560
  }
1484
1561
  if (adapter.id !== "static") {
1485
1562
  lines.push("- `pracht generate api --path /health --methods GET` — add an API route");
@@ -1505,7 +1582,8 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1505
1582
  lines.push("This app uses **pages routing** (file-system based).");
1506
1583
  lines.push("");
1507
1584
  lines.push("- `src/pages/` — file-system routes (each file becomes a route)");
1508
- lines.push("- `src/pages/_app.tsx` — app shell (layout and head)");
1585
+ lines.push("- `src/pages/_app.tsx` — app shell (layout and head); an `_app.tsx` in a");
1586
+ lines.push(" subdirectory replaces it for that subtree");
1509
1587
  lines.push(
1510
1588
  "- `src/pages/404.tsx` — not-found page, wired automatically (never a URL of its own)",
1511
1589
  );
@@ -1566,10 +1644,19 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1566
1644
  lines.push("## Agent tooling");
1567
1645
  lines.push("");
1568
1646
  lines.push(
1569
- "- `.claude/skills/` — pracht Claude Code skills (audits, scaffolds, testing, debugging); invoke with `/<skill-name>`",
1647
+ agentSkills === "full"
1648
+ ? "- `.claude/skills/` — the full pracht skill catalog (audits, scaffolds, testing, debugging); invoke with `/<skill-name>`"
1649
+ : `- \`.claude/skills/\` — the ${CORE_SKILLS.length} core pracht skills (${CORE_SKILLS.join(", ")}); invoke with \`/<skill-name>\``,
1570
1650
  );
1651
+ if (agentSkills !== "full") {
1652
+ lines.push(
1653
+ "- More skills — audits, testing scaffolds, and the `add-*` integrations — are published at " +
1654
+ `${SKILL_CATALOG_URL}. Run \`pracht skills list\` to see the catalog and ` +
1655
+ "`pracht skills add <name...>` to install one; do not hand-write a SKILL.md that already exists there.",
1656
+ );
1657
+ }
1571
1658
  lines.push(
1572
- "- `.mcp.json` — registers the `pracht mcp` server so MCP clients can inspect the app graph, run doctor/verify, and scaffold natively",
1659
+ "- `.mcp.json` — registers the `pracht dev-mcp` server so MCP clients can inspect the app graph, run doctor/verify, and scaffold natively",
1573
1660
  );
1574
1661
  }
1575
1662
 
@@ -1580,6 +1667,7 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1580
1667
 
1581
1668
  function createReadme({
1582
1669
  adapter,
1670
+ agentSkills = "core",
1583
1671
  agentTools,
1584
1672
  packageManager,
1585
1673
  pnpmMajor,
@@ -1598,7 +1686,13 @@ function createReadme({
1598
1686
  : `${packageManager} build`;
1599
1687
  const previewCommand = packageManager === "npm" ? "npm run preview" : `${packageManager} preview`;
1600
1688
  const startCommand = packageManager === "npm" ? "npm run start" : `${packageManager} start`;
1601
- const deployCommand = packageManager === "npm" ? "npm run deploy" : `${packageManager} deploy`;
1689
+ // `pnpm deploy` is pnpm's own workspace-deploy command and shadows the
1690
+ // package script exactly the way `bun build` shadows `build`, so both need
1691
+ // the explicit `run` form. `yarn deploy` has no builtin to collide with.
1692
+ const deployCommand =
1693
+ packageManager === "npm" || packageManager === "pnpm" || packageManager === "bun"
1694
+ ? `${packageManager} run deploy`
1695
+ : `${packageManager} deploy`;
1602
1696
  const typecheckCommand =
1603
1697
  packageManager === "npm" ? "npm run typecheck" : `${packageManager} typecheck`;
1604
1698
  // The pages router derives route ids from filenames, so its home page is
@@ -1687,19 +1781,15 @@ function createReadme({
1687
1781
  lines.push("- `src/api/health.ts` is a sample API route.");
1688
1782
  }
1689
1783
 
1690
- // The one convention a new app trips over before it writes anything else,
1691
- // and AGENTS.md where the same note lives for coding agents — is only
1692
- // seeded when agent tooling is enabled.
1693
- lines.push("");
1694
- lines.push("## Navigating");
1695
- lines.push("");
1696
- lines.push(
1697
- `Pracht navigates by route id, not by path: \`<Link route="${homeRouteId}">\`, ` +
1698
- `\`href("${homeRouteId}")\`, \`navigate({ route: "${homeRouteId}" })\`. Dynamic routes ` +
1699
- "take their segments through `params`. The id survives a path change, and `pracht " +
1700
- "typegen` types both the id and its params — so `<Link href>` is a compile error. Use a " +
1701
- "plain `<a href>` for external and user-provided URLs.",
1702
- );
1784
+ if (tailwind) {
1785
+ lines.push("- `src/styles/global.css` is the Tailwind CSS entry, imported by the shell.");
1786
+ }
1787
+
1788
+ if (agentTools) {
1789
+ lines.push(
1790
+ "- `.claude/skills/` and `.mcp.json` wire up the pracht Claude Code skills and MCP server.",
1791
+ );
1792
+ }
1703
1793
 
1704
1794
  if (packageManager === "pnpm") {
1705
1795
  lines.push(
@@ -1711,16 +1801,46 @@ function createReadme({
1711
1801
  );
1712
1802
  }
1713
1803
 
1714
- if (tailwind) {
1715
- lines.push("- `src/styles/global.css` is the Tailwind CSS entry, imported by the shell.");
1716
- }
1717
-
1718
1804
  if (agentTools) {
1805
+ lines.push("");
1806
+ lines.push("## Skills");
1807
+ lines.push("");
1719
1808
  lines.push(
1720
- "- `.claude/skills/` and `.mcp.json` wire up the pracht Claude Code skills and MCP server.",
1809
+ agentSkills === "full"
1810
+ ? "The whole pracht skill catalog is in `.claude/skills/`. Browse it with `pracht skills list`."
1811
+ : `\`.claude/skills/\` holds the ${CORE_SKILLS.length} core skills: ` +
1812
+ `${CORE_SKILLS.map((name) => `\`/${name}\``).join(", ")}.`,
1721
1813
  );
1814
+ if (agentSkills !== "full") {
1815
+ lines.push("");
1816
+ lines.push(
1817
+ "The rest of the catalog — audits, testing scaffolds, and the `add-*` " +
1818
+ "integrations — is published at " +
1819
+ `[\`/.well-known/agent-skills/index.json\`](${SKILL_CATALOG_URL}). ` +
1820
+ "Install what you need:",
1821
+ );
1822
+ lines.push("");
1823
+ lines.push("```bash");
1824
+ lines.push("pracht skills list");
1825
+ lines.push("pracht skills add audit-loaders add-db");
1826
+ lines.push("```");
1827
+ }
1722
1828
  }
1723
1829
 
1830
+ // The one convention a new app trips over before it writes anything else,
1831
+ // and AGENTS.md — where the same note lives for coding agents — is only
1832
+ // seeded when agent tooling is enabled.
1833
+ lines.push("");
1834
+ lines.push("## Navigating");
1835
+ lines.push("");
1836
+ lines.push(
1837
+ `Pracht navigates by route id, not by path: \`<Link route="${homeRouteId}">\`, ` +
1838
+ `\`href("${homeRouteId}")\`, \`navigate({ route: "${homeRouteId}" })\`. Dynamic routes ` +
1839
+ "take their segments through `params`. The id survives a path change, and `pracht " +
1840
+ "typegen` types both the id and its params — so `<Link href>` is a compile error. Use a " +
1841
+ "plain `<a href>` for external and user-provided URLs.",
1842
+ );
1843
+
1724
1844
  lines.push("");
1725
1845
  lines.push("## Checks");
1726
1846
  lines.push("");
@@ -1824,6 +1944,7 @@ async function installDependencies(targetDir, packageManager) {
1824
1944
 
1825
1945
  function printNextSteps({
1826
1946
  adapter,
1947
+ agentSkills = "core",
1827
1948
  agentTools,
1828
1949
  dir,
1829
1950
  installSucceeded,
@@ -1843,13 +1964,21 @@ function printNextSteps({
1843
1964
  `Router: ${router === "pages" ? "pages (file-system)" : "manifest (src/routes.ts)"}`,
1844
1965
  );
1845
1966
  console.log(`Tailwind: ${tailwind ? "yes" : "no"}`);
1846
- console.log(`Agent tooling: ${agentTools ? "skills, .mcp.json, AGENTS.md" : "none"}`);
1967
+ console.log(
1968
+ `Agent tooling: ${
1969
+ agentTools
1970
+ ? `${agentSkills === "full" ? "full skill catalog" : `${CORE_SKILLS.length} core skills`}, .mcp.json, AGENTS.md`
1971
+ : "none"
1972
+ }`,
1973
+ );
1847
1974
  if (router === "pages") {
1848
1975
  console.log("");
1849
1976
  console.log(
1850
- "Note: the pages router has no manifest, so middleware, capabilities, constraints, and\n" +
1851
- "the agent surface (capability endpoints, WebMCP, remote MCP, `pracht eval`) are not\n" +
1852
- "available. Scaffold with --router=manifest if you need them.",
1977
+ "Note: the pages router registers by file `_app.tsx` per directory, one root\n" +
1978
+ "`_middleware.ts` for every page route, `src/capabilities/`, and\n" +
1979
+ "`src/pages/_app.config.ts` for agents and constraints. Per-route middleware\n" +
1980
+ "assignment, per-route shell overrides, path-prefix groups, and webhook ISG\n" +
1981
+ "still need a manifest; scaffold with --router=manifest, or eject later.",
1853
1982
  );
1854
1983
  }
1855
1984
  console.log("");
@@ -1899,8 +2028,10 @@ Options:
1899
2028
  --template=minimal|tailwind Choose starter template (minimal, or minimal + Tailwind CSS)
1900
2029
  --tailwind / --no-tailwind Enable or disable Tailwind CSS wiring (default: prompt).
1901
2030
  Sets the same thing as --template; the last one wins.
1902
- --agent-tools / --no-agent-tools
1903
- Seed Claude Code skills and a pracht MCP config (default: prompt, yes)
2031
+ --agent-tools[=core|full] / --no-agent-tools
2032
+ Seed Claude Code skills and a pracht MCP config (default: prompt, yes).
2033
+ core (the default) seeds ${CORE_SKILLS.length} skills; full seeds the whole catalog.
2034
+ Add more later with \`pracht skills add <name>\`.
1904
2035
  --no-git Skip git init and the initial commit
1905
2036
  --skip-install Skip dependency installation
1906
2037
  --yes, -y Accept defaults, skip all prompts