create-pracht 0.6.3 → 0.7.1

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,
@@ -705,6 +754,7 @@ async function buildProjectFiles({
705
754
  }),
706
755
  "vite.config.ts": createViteConfig(adapter, router, tailwind),
707
756
  "tsconfig.json": createBaseTSConfig(adapter),
757
+ "tsconfig.client.json": createClientTSConfig(router),
708
758
  };
709
759
 
710
760
  // A static export has no server, so an API route would be a hard build
@@ -716,6 +766,7 @@ async function buildProjectFiles({
716
766
  if (agentTools) {
717
767
  files["AGENTS.md"] = createAgentInstructions({
718
768
  adapter,
769
+ agentSkills,
719
770
  agentTools,
720
771
  packageManager,
721
772
  router,
@@ -754,15 +805,19 @@ async function buildProjectFiles({
754
805
 
755
806
  if (agentTools) {
756
807
  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) {
808
+ Object.assign(files, await readSkillFiles(agentSkills));
809
+ }
810
+
811
+ // Only pnpm reads this file; npm, yarn, and bun ignore it entirely, so
812
+ // emitting it for them leaves a config in the repo that nothing in the repo
813
+ // obeys and that a reader has to look up to dismiss.
814
+ //
815
+ // pnpm itself resolves build-script policy from the workspace root, so inside
816
+ // an existing workspace our own file would be read by nobody — and `pnpm
817
+ // install` run from the app directory would find it first and re-root the
818
+ // workspace there, detaching the app from its siblings. Decided here so the
819
+ // `--json` and `--dry-run` listings match what is actually written.
820
+ if (packageManager === "pnpm" && !pnpmWorkspaceNotice) {
766
821
  files["pnpm-workspace.yaml"] = createPnpmWorkspaceConfig(adapter, tailwind, policyMajor);
767
822
  }
768
823
 
@@ -781,7 +836,7 @@ function createMcpConfig() {
781
836
  // than the one the app builds with. Not bare `npx pracht` either:
782
837
  // that resolves to a registry package literally named `pracht`
783
838
  // whenever the local bin is missing — `--no-install` fails loudly.
784
- args: ["--no-install", "pracht", "mcp"],
839
+ args: ["--no-install", "pracht", "dev-mcp"],
785
840
  },
786
841
  },
787
842
  },
@@ -790,15 +845,19 @@ function createMcpConfig() {
790
845
  )}\n`;
791
846
  }
792
847
 
793
- async function readSkillFiles() {
848
+ async function readSkillFiles(scope = "core") {
794
849
  const skillsDir = SKILL_DIRS.find((dir) => existsSync(dir));
795
850
 
796
851
  if (!skillsDir) {
797
852
  return {};
798
853
  }
799
854
 
855
+ const wanted = scope === "full" ? null : new Set(CORE_SKILLS);
800
856
  const files = {};
801
857
  for (const name of await readdir(skillsDir)) {
858
+ if (wanted && !wanted.has(name)) {
859
+ continue;
860
+ }
802
861
  const skillFile = resolve(skillsDir, name, "SKILL.md");
803
862
  if (!existsSync(skillFile)) {
804
863
  continue;
@@ -813,7 +872,7 @@ function createPackageJson({ adapter, projectName, tailwind, versions }) {
813
872
  const scripts = {
814
873
  build: "pracht build",
815
874
  dev: "pracht dev",
816
- typecheck: "tsc --noEmit",
875
+ typecheck: "tsc --noEmit && tsc --noEmit --project tsconfig.client.json",
817
876
  };
818
877
 
819
878
  if (adapter.id === "node") {
@@ -856,18 +915,20 @@ function createPackageJson({ adapter, projectName, tailwind, versions }) {
856
915
  devDependencies.tailwindcss = versions["tailwindcss"];
857
916
  }
858
917
 
918
+ // Conventional key order, not alphabetical: `name` belongs at the top of a
919
+ // package.json, not between devDependencies and private.
859
920
  return `${JSON.stringify(
860
921
  {
922
+ name: projectName,
923
+ version: "0.0.0",
924
+ private: true,
925
+ type: "module",
926
+ scripts,
861
927
  dependencies: {
862
928
  [adapter.packageName]: versions[adapter.packageName],
863
929
  "@pracht/core": versions["@pracht/core"],
864
930
  },
865
931
  devDependencies,
866
- name: projectName,
867
- private: true,
868
- scripts,
869
- type: "module",
870
- version: "0.0.0",
871
932
  },
872
933
  null,
873
934
  2,
@@ -1082,23 +1143,47 @@ function createPagesHomeRoute(adapter) {
1082
1143
  }
1083
1144
 
1084
1145
  function createBaseTSConfig(_adapter) {
1146
+ // Two-space and trailing-newline like every other generated file, and grouped
1147
+ // the way tsconfig documentation presents these options rather than
1148
+ // alphabetically.
1085
1149
  const config = {
1086
1150
  compilerOptions: {
1087
- allowImportingTsExtensions: true,
1088
- jsx: "react-jsx",
1089
- jsxImportSource: "preact",
1151
+ target: "ES2022",
1090
1152
  lib: ["ES2022", "DOM", "DOM.Iterable"],
1091
1153
  module: "ESNext",
1092
1154
  moduleResolution: "Bundler",
1093
- noEmit: true,
1094
- skipLibCheck: true,
1095
- strict: true,
1096
- target: "ES2022",
1155
+ jsx: "react-jsx",
1156
+ jsxImportSource: "preact",
1097
1157
  types: ["vite/client", "@pracht/vite-plugin/virtual"],
1158
+ allowImportingTsExtensions: true,
1098
1159
  verbatimModuleSyntax: true,
1160
+ strict: true,
1161
+ skipLibCheck: true,
1162
+ noEmit: true,
1099
1163
  },
1100
1164
  };
1101
- return JSON.stringify(config, null, 4);
1165
+ return `${JSON.stringify(config, null, 2)}\n`;
1166
+ }
1167
+
1168
+ function createClientTSConfig(router) {
1169
+ const config = {
1170
+ extends: "./tsconfig.json",
1171
+ compilerOptions: {
1172
+ customConditions: ["browser"],
1173
+ },
1174
+ include:
1175
+ router === "pages"
1176
+ ? ["src/pages/**/*", "src/islands/**/*"]
1177
+ : ["src/routes/**/*", "src/shells/**/*", "src/islands/**/*"],
1178
+ };
1179
+
1180
+ // Pages route modules are shared with the browser, but these two convention
1181
+ // files are server-only. The base program still checks the whole project.
1182
+ if (router === "pages") {
1183
+ config.exclude = ["src/pages/**/_app.config.*", "src/pages/**/_middleware.*"];
1184
+ }
1185
+
1186
+ return `${JSON.stringify(config, null, 2)}\n`;
1102
1187
  }
1103
1188
 
1104
1189
  function createHealthRoute(adapter) {
@@ -1426,16 +1511,26 @@ function createDockerignore() {
1426
1511
  }
1427
1512
 
1428
1513
  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.";
1514
+ "**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
1515
 
1431
1516
  const PAGES_ROUTER_ISG_POLICY =
1432
1517
  '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
1518
 
1434
- function createAgentInstructions({ adapter, agentTools, packageManager, router, tailwind }) {
1519
+ function createAgentInstructions({
1520
+ adapter,
1521
+ agentSkills = "core",
1522
+ agentTools,
1523
+ packageManager,
1524
+ router,
1525
+ tailwind,
1526
+ }) {
1435
1527
  // `bun build` is Bun's own bundler and shadows the package script, so bun
1436
1528
  // needs the explicit `run` form the same way npm does.
1437
1529
  const runCmd =
1438
1530
  packageManager === "npm" || packageManager === "bun" ? `${packageManager} run` : packageManager;
1531
+ // `pnpm deploy` is pnpm's own workspace-deploy command and shadows the
1532
+ // package script, so this one script needs `run` for pnpm too.
1533
+ const deployCmd = packageManager === "pnpm" ? "pnpm run" : runCmd;
1439
1534
  // The pages router derives route ids from filenames, so the home page of a
1440
1535
  // pages app is `index` (`src/pages/index.tsx`); the manifest scaffold names
1441
1536
  // it `home` explicitly. Every id in the instructions below has to be one the
@@ -1466,7 +1561,7 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1466
1561
  }
1467
1562
 
1468
1563
  if (adapter.id === "cloudflare" || adapter.id === "netlify" || adapter.id === "vercel") {
1469
- lines.push(`- \`${runCmd} deploy\` — build and deploy`);
1564
+ lines.push(`- \`${deployCmd} deploy\` — build and deploy`);
1470
1565
  }
1471
1566
 
1472
1567
  lines.push("");
@@ -1480,6 +1575,10 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1480
1575
  if (adapter.id !== "static") {
1481
1576
  lines.push("- `pracht generate middleware --name auth` — add middleware");
1482
1577
  }
1578
+ } else if (adapter.id !== "static") {
1579
+ lines.push(
1580
+ "- `pracht generate middleware --name _middleware` — add root middleware for every page route",
1581
+ );
1483
1582
  }
1484
1583
  if (adapter.id !== "static") {
1485
1584
  lines.push("- `pracht generate api --path /health --methods GET` — add an API route");
@@ -1505,7 +1604,8 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1505
1604
  lines.push("This app uses **pages routing** (file-system based).");
1506
1605
  lines.push("");
1507
1606
  lines.push("- `src/pages/` — file-system routes (each file becomes a route)");
1508
- lines.push("- `src/pages/_app.tsx` — app shell (layout and head)");
1607
+ lines.push("- `src/pages/_app.tsx` — app shell (layout and head); an `_app.tsx` in a");
1608
+ lines.push(" subdirectory replaces it for that subtree");
1509
1609
  lines.push(
1510
1610
  "- `src/pages/404.tsx` — not-found page, wired automatically (never a URL of its own)",
1511
1611
  );
@@ -1528,6 +1628,10 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1528
1628
  lines.push("- `src/api/` — API route handlers");
1529
1629
  }
1530
1630
  lines.push(`- \`vite.config.ts\` — Vite config with the ${adapter.label} adapter`);
1631
+ lines.push("- `tsconfig.json` — server-capable whole-project TypeScript checks");
1632
+ lines.push(
1633
+ "- `tsconfig.client.json` — browser-conditioned checks for routes, shells, islands, and their imports",
1634
+ );
1531
1635
 
1532
1636
  if (tailwind) {
1533
1637
  lines.push("- `src/styles/global.css` — Tailwind CSS entry stylesheet, imported by the shell");
@@ -1566,10 +1670,19 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1566
1670
  lines.push("## Agent tooling");
1567
1671
  lines.push("");
1568
1672
  lines.push(
1569
- "- `.claude/skills/` — pracht Claude Code skills (audits, scaffolds, testing, debugging); invoke with `/<skill-name>`",
1673
+ agentSkills === "full"
1674
+ ? "- `.claude/skills/` — the full pracht skill catalog (audits, scaffolds, testing, debugging); invoke with `/<skill-name>`"
1675
+ : `- \`.claude/skills/\` — the ${CORE_SKILLS.length} core pracht skills (${CORE_SKILLS.join(", ")}); invoke with \`/<skill-name>\``,
1570
1676
  );
1677
+ if (agentSkills !== "full") {
1678
+ lines.push(
1679
+ "- More skills — audits, testing scaffolds, and the `add-*` integrations — are published at " +
1680
+ `${SKILL_CATALOG_URL}. Run \`pracht skills list\` to see the catalog and ` +
1681
+ "`pracht skills add <name...>` to install one; do not hand-write a SKILL.md that already exists there.",
1682
+ );
1683
+ }
1571
1684
  lines.push(
1572
- "- `.mcp.json` — registers the `pracht mcp` server so MCP clients can inspect the app graph, run doctor/verify, and scaffold natively",
1685
+ "- `.mcp.json` — registers the `pracht dev-mcp` server so MCP clients can inspect the app graph, run doctor/verify, and scaffold natively",
1573
1686
  );
1574
1687
  }
1575
1688
 
@@ -1580,6 +1693,7 @@ function createAgentInstructions({ adapter, agentTools, packageManager, router,
1580
1693
 
1581
1694
  function createReadme({
1582
1695
  adapter,
1696
+ agentSkills = "core",
1583
1697
  agentTools,
1584
1698
  packageManager,
1585
1699
  pnpmMajor,
@@ -1598,7 +1712,13 @@ function createReadme({
1598
1712
  : `${packageManager} build`;
1599
1713
  const previewCommand = packageManager === "npm" ? "npm run preview" : `${packageManager} preview`;
1600
1714
  const startCommand = packageManager === "npm" ? "npm run start" : `${packageManager} start`;
1601
- const deployCommand = packageManager === "npm" ? "npm run deploy" : `${packageManager} deploy`;
1715
+ // `pnpm deploy` is pnpm's own workspace-deploy command and shadows the
1716
+ // package script exactly the way `bun build` shadows `build`, so both need
1717
+ // the explicit `run` form. `yarn deploy` has no builtin to collide with.
1718
+ const deployCommand =
1719
+ packageManager === "npm" || packageManager === "pnpm" || packageManager === "bun"
1720
+ ? `${packageManager} run deploy`
1721
+ : `${packageManager} deploy`;
1602
1722
  const typecheckCommand =
1603
1723
  packageManager === "npm" ? "npm run typecheck" : `${packageManager} typecheck`;
1604
1724
  // The pages router derives route ids from filenames, so its home page is
@@ -1687,20 +1807,21 @@ function createReadme({
1687
1807
  lines.push("- `src/api/health.ts` is a sample API route.");
1688
1808
  }
1689
1809
 
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("");
1810
+ lines.push("- `tsconfig.json` — server-capable whole-project TypeScript checks.");
1696
1811
  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.",
1812
+ "- `tsconfig.client.json` — browser-conditioned checks for routes, shells, islands, and their imports.",
1702
1813
  );
1703
1814
 
1815
+ if (tailwind) {
1816
+ lines.push("- `src/styles/global.css` is the Tailwind CSS entry, imported by the shell.");
1817
+ }
1818
+
1819
+ if (agentTools) {
1820
+ lines.push(
1821
+ "- `.claude/skills/` and `.mcp.json` wire up the pracht Claude Code skills and MCP server.",
1822
+ );
1823
+ }
1824
+
1704
1825
  if (packageManager === "pnpm") {
1705
1826
  lines.push(
1706
1827
  pnpmWorkspaceNotice
@@ -1711,16 +1832,46 @@ function createReadme({
1711
1832
  );
1712
1833
  }
1713
1834
 
1714
- if (tailwind) {
1715
- lines.push("- `src/styles/global.css` is the Tailwind CSS entry, imported by the shell.");
1716
- }
1717
-
1718
1835
  if (agentTools) {
1836
+ lines.push("");
1837
+ lines.push("## Skills");
1838
+ lines.push("");
1719
1839
  lines.push(
1720
- "- `.claude/skills/` and `.mcp.json` wire up the pracht Claude Code skills and MCP server.",
1840
+ agentSkills === "full"
1841
+ ? "The whole pracht skill catalog is in `.claude/skills/`. Browse it with `pracht skills list`."
1842
+ : `\`.claude/skills/\` holds the ${CORE_SKILLS.length} core skills: ` +
1843
+ `${CORE_SKILLS.map((name) => `\`/${name}\``).join(", ")}.`,
1721
1844
  );
1845
+ if (agentSkills !== "full") {
1846
+ lines.push("");
1847
+ lines.push(
1848
+ "The rest of the catalog — audits, testing scaffolds, and the `add-*` " +
1849
+ "integrations — is published at " +
1850
+ `[\`/.well-known/agent-skills/index.json\`](${SKILL_CATALOG_URL}). ` +
1851
+ "Install what you need:",
1852
+ );
1853
+ lines.push("");
1854
+ lines.push("```bash");
1855
+ lines.push("pracht skills list");
1856
+ lines.push("pracht skills add audit-loaders add-db");
1857
+ lines.push("```");
1858
+ }
1722
1859
  }
1723
1860
 
1861
+ // The one convention a new app trips over before it writes anything else,
1862
+ // and AGENTS.md — where the same note lives for coding agents — is only
1863
+ // seeded when agent tooling is enabled.
1864
+ lines.push("");
1865
+ lines.push("## Navigating");
1866
+ lines.push("");
1867
+ lines.push(
1868
+ `Pracht navigates by route id, not by path: \`<Link route="${homeRouteId}">\`, ` +
1869
+ `\`href("${homeRouteId}")\`, \`navigate({ route: "${homeRouteId}" })\`. Dynamic routes ` +
1870
+ "take their segments through `params`. The id survives a path change, and `pracht " +
1871
+ "typegen` types both the id and its params — so `<Link href>` is a compile error. Use a " +
1872
+ "plain `<a href>` for external and user-provided URLs.",
1873
+ );
1874
+
1724
1875
  lines.push("");
1725
1876
  lines.push("## Checks");
1726
1877
  lines.push("");
@@ -1824,6 +1975,7 @@ async function installDependencies(targetDir, packageManager) {
1824
1975
 
1825
1976
  function printNextSteps({
1826
1977
  adapter,
1978
+ agentSkills = "core",
1827
1979
  agentTools,
1828
1980
  dir,
1829
1981
  installSucceeded,
@@ -1843,13 +1995,21 @@ function printNextSteps({
1843
1995
  `Router: ${router === "pages" ? "pages (file-system)" : "manifest (src/routes.ts)"}`,
1844
1996
  );
1845
1997
  console.log(`Tailwind: ${tailwind ? "yes" : "no"}`);
1846
- console.log(`Agent tooling: ${agentTools ? "skills, .mcp.json, AGENTS.md" : "none"}`);
1998
+ console.log(
1999
+ `Agent tooling: ${
2000
+ agentTools
2001
+ ? `${agentSkills === "full" ? "full skill catalog" : `${CORE_SKILLS.length} core skills`}, .mcp.json, AGENTS.md`
2002
+ : "none"
2003
+ }`,
2004
+ );
1847
2005
  if (router === "pages") {
1848
2006
  console.log("");
1849
2007
  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.",
2008
+ "Note: the pages router registers by file — `_app.tsx` per directory, one root\n" +
2009
+ "`_middleware.ts` for every page route, `src/capabilities/`, and\n" +
2010
+ "`src/pages/_app.config.ts` for agents and constraints. Per-route middleware\n" +
2011
+ "assignment, per-route shell overrides, path-prefix groups, and webhook ISG\n" +
2012
+ "still need a manifest; scaffold with --router=manifest, or eject later.",
1853
2013
  );
1854
2014
  }
1855
2015
  console.log("");
@@ -1899,8 +2059,10 @@ Options:
1899
2059
  --template=minimal|tailwind Choose starter template (minimal, or minimal + Tailwind CSS)
1900
2060
  --tailwind / --no-tailwind Enable or disable Tailwind CSS wiring (default: prompt).
1901
2061
  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)
2062
+ --agent-tools[=core|full] / --no-agent-tools
2063
+ Seed Claude Code skills and a pracht MCP config (default: prompt, yes).
2064
+ core (the default) seeds ${CORE_SKILLS.length} skills; full seeds the whole catalog.
2065
+ Add more later with \`pracht skills add <name>\`.
1904
2066
  --no-git Skip git init and the initial commit
1905
2067
  --skip-install Skip dependency installation
1906
2068
  --yes, -y Accept defaults, skip all prompts