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/README.md +7 -5
- package/package.json +4 -1
- package/skills/add-auth/SKILL.md +173 -121
- package/skills/add-capabilities/SKILL.md +107 -96
- package/skills/add-observability/SKILL.md +12 -39
- package/skills/audit-agent-surface/SKILL.md +21 -16
- package/skills/audit-auth/SKILL.md +68 -12
- package/skills/audit-bundles/SKILL.md +1 -1
- package/skills/audit-shells/SKILL.md +12 -5
- package/skills/migrate-nextjs/SKILL.md +30 -10
- package/skills/pracht-scaffold/SKILL.md +11 -1
- package/skills/tune-render-mode/SKILL.md +36 -0
- package/skills/typed-routes/SKILL.md +3 -2
- package/skills/upgrade-pracht/SKILL.md +18 -8
- package/src/index.js +192 -61
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) —
|
|
492
|
-
console.log("
|
|
493
|
-
console.log(" 2. Pages (file-system routing) —
|
|
494
|
-
console.log("
|
|
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 =
|
|
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
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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,
|
|
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
|
|
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({
|
|
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(`- \`${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
1851
|
-
"
|
|
1852
|
-
"
|
|
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
|