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/README.md +9 -7
- package/package.json +4 -1
- package/skills/add-auth/SKILL.md +173 -121
- package/skills/add-capabilities/SKILL.md +108 -96
- package/skills/add-observability/SKILL.md +12 -39
- package/skills/audit-agent-surface/SKILL.md +23 -17
- package/skills/audit-auth/SKILL.md +68 -12
- package/skills/audit-bundles/SKILL.md +11 -1
- package/skills/audit-shells/SKILL.md +12 -5
- package/skills/migrate-nextjs/SKILL.md +30 -10
- package/skills/pracht-debug/SKILL.md +17 -1
- package/skills/pracht-scaffold/SKILL.md +11 -1
- package/skills/pre-deploy/SKILL.md +15 -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 +221 -59
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,
|
|
@@ -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
|
|
761
|
-
//
|
|
762
|
-
//
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1094
|
-
|
|
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,
|
|
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
|
|
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({
|
|
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(`- \`${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
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(
|
|
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
|
|
1851
|
-
"
|
|
1852
|
-
"
|
|
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
|