create-theokit 0.7.0 → 0.8.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/dist/cli.js +296 -33
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/default/.prettierrc +7 -0
- package/templates/default/AGENTS.md +96 -0
- package/templates/default/app.ts +7 -12
- package/templates/default/eslint.config.mjs +14 -0
- package/templates/default/package.json.tmpl +18 -6
- package/templates/default/server/agents/assistant.agent.ts +2 -5
- package/templates/default/server/controllers/tasks.controller.ts +2 -2
- package/templates/default/server/filters/http-error.filter.ts +1 -1
- package/templates/default/server/guards/auth.guard.ts +1 -1
- package/templates/default/server/index.ts +18 -0
- package/templates/default/server/interceptors/timing.interceptor.ts +1 -1
- package/templates/default/server/middleware/logger.middleware.ts +1 -1
- package/templates/default/server/toolboxes/task.tools.ts +1 -1
- package/templates/default/tsconfig.json +7 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import {
|
|
6
|
+
readFileSync as readFileSync4,
|
|
7
|
+
writeFileSync as writeFileSync4,
|
|
8
|
+
unlinkSync as unlinkSync4,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
renameSync as renameSync2,
|
|
11
|
+
existsSync as existsSync4,
|
|
12
|
+
rmSync as rmSync2
|
|
13
|
+
} from "fs";
|
|
14
|
+
import { resolve as resolve3, join as join4 } from "path";
|
|
5
15
|
|
|
6
16
|
// src/install.ts
|
|
7
17
|
import spawn from "cross-spawn";
|
|
@@ -52,6 +62,65 @@ function assertNodeVersion(currentRaw, minimum = MIN_NODE_VERSION) {
|
|
|
52
62
|
}
|
|
53
63
|
}
|
|
54
64
|
|
|
65
|
+
// src/prompts.ts
|
|
66
|
+
import { createInterface } from "readline";
|
|
67
|
+
var DEFAULTS = {
|
|
68
|
+
typescript: true,
|
|
69
|
+
eslint: true,
|
|
70
|
+
tailwind: true,
|
|
71
|
+
srcDir: false,
|
|
72
|
+
aliases: true,
|
|
73
|
+
agentsMd: true
|
|
74
|
+
};
|
|
75
|
+
async function ask(rl, question, defaultVal) {
|
|
76
|
+
return new Promise((resolve4) => {
|
|
77
|
+
const suffix = defaultVal ? "(Y/n)" : "(y/N)";
|
|
78
|
+
rl.question(` ${question} ${suffix} `, (answer) => {
|
|
79
|
+
const a = answer.trim().toLowerCase();
|
|
80
|
+
if (a === "") resolve4(defaultVal);
|
|
81
|
+
else resolve4(a === "y" || a === "yes");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function askChoice(rl, question, choices, defaultIdx = 0) {
|
|
86
|
+
return new Promise((resolve4) => {
|
|
87
|
+
console.log(` ${question}`);
|
|
88
|
+
for (let i = 0; i < choices.length; i++) {
|
|
89
|
+
const marker = i === defaultIdx ? "\u276F" : " ";
|
|
90
|
+
console.log(` ${marker} ${choices[i]}`);
|
|
91
|
+
}
|
|
92
|
+
rl.question(" Choice (number): ", (answer) => {
|
|
93
|
+
const n = parseInt(answer.trim(), 10);
|
|
94
|
+
resolve4(n >= 1 && n <= choices.length ? n - 1 : defaultIdx);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
async function runPrompts(projectName) {
|
|
99
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
100
|
+
try {
|
|
101
|
+
console.log("");
|
|
102
|
+
const mode = await askChoice(rl, "Would you like to use the recommended TheoKit defaults?", [
|
|
103
|
+
"Yes, use recommended defaults \u2014 TypeScript, ESLint, Tailwind CSS, AGENTS.md",
|
|
104
|
+
"No, customize settings"
|
|
105
|
+
], 0);
|
|
106
|
+
if (mode === 0) {
|
|
107
|
+
return { projectName, ...DEFAULTS };
|
|
108
|
+
}
|
|
109
|
+
const typescript = await ask(rl, "Would you like to use TypeScript?", true);
|
|
110
|
+
const eslint = await ask(rl, "Would you like to use ESLint?", true);
|
|
111
|
+
const tailwind = await ask(rl, "Would you like to use Tailwind CSS?", true);
|
|
112
|
+
const srcDir = await ask(rl, "Would you like your code inside a `src/` directory?", false);
|
|
113
|
+
const aliases = await ask(rl, "Would you like to use import alias (@/*)?", true);
|
|
114
|
+
const agentsMd = await ask(rl, "Would you like to include AGENTS.md for coding agents?", true);
|
|
115
|
+
return { projectName, typescript, eslint, tailwind, srcDir, aliases, agentsMd };
|
|
116
|
+
} finally {
|
|
117
|
+
rl.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function getDefaults(projectName) {
|
|
121
|
+
return { projectName, ...DEFAULTS };
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
// src/scaffold-services.ts
|
|
56
125
|
import {
|
|
57
126
|
cpSync,
|
|
@@ -331,7 +400,47 @@ Original error: ${original}`
|
|
|
331
400
|
}
|
|
332
401
|
|
|
333
402
|
// src/cli.ts
|
|
334
|
-
|
|
403
|
+
var VERSION = "0.8.0";
|
|
404
|
+
function getFlag(args, name) {
|
|
405
|
+
const flag = args.find((a) => a.startsWith(`--${name}=`));
|
|
406
|
+
return flag ? flag.split("=")[1] : void 0;
|
|
407
|
+
}
|
|
408
|
+
function hasFlag(args, ...names) {
|
|
409
|
+
return names.some((n) => args.includes(`--${n}`) || args.includes(`-${n}`));
|
|
410
|
+
}
|
|
411
|
+
function showHelp() {
|
|
412
|
+
console.log(`
|
|
413
|
+
create-theokit v${VERSION}
|
|
414
|
+
|
|
415
|
+
Usage: create-theokit <project-name> [options]
|
|
416
|
+
|
|
417
|
+
Options:
|
|
418
|
+
-h, --help Show this help message
|
|
419
|
+
-v, --version Show version number
|
|
420
|
+
--yes Use recommended defaults (skip prompts)
|
|
421
|
+
--template=<name> Template to use (default: "default")
|
|
422
|
+
--bare Minimal app (no @theokit/* deps)
|
|
423
|
+
--skip-install Scaffold files only, skip package install
|
|
424
|
+
--disable-git Skip git init
|
|
425
|
+
--use-npm Use npm as package manager
|
|
426
|
+
--use-pnpm Use pnpm as package manager
|
|
427
|
+
--use-yarn Use yarn as package manager
|
|
428
|
+
--use-bun Use bun as package manager
|
|
429
|
+
--import-alias=<alias> Import alias (default: "@/*")
|
|
430
|
+
--example=<name|github-url> Bootstrap from a GitHub example
|
|
431
|
+
--biome Use Biome instead of ESLint
|
|
432
|
+
--agents-md Include AGENTS.md (default: true)
|
|
433
|
+
|
|
434
|
+
Examples:
|
|
435
|
+
npx create-theokit my-app --yes
|
|
436
|
+
npx create-theokit my-app
|
|
437
|
+
npx create-theokit my-app --bare --skip-install
|
|
438
|
+
npx create-theokit my-app --use-bun --biome
|
|
439
|
+
npx create-theokit my-app --example=https://github.com/user/repo
|
|
440
|
+
npx create-theokit my-app --import-alias="~/*"
|
|
441
|
+
`);
|
|
442
|
+
}
|
|
443
|
+
async function main() {
|
|
335
444
|
try {
|
|
336
445
|
assertNodeVersion(process.version);
|
|
337
446
|
} catch (err) {
|
|
@@ -340,32 +449,33 @@ function main() {
|
|
|
340
449
|
process.exit(1);
|
|
341
450
|
}
|
|
342
451
|
const args = process.argv.slice(2);
|
|
343
|
-
|
|
452
|
+
if (hasFlag(args, "h", "help")) {
|
|
453
|
+
showHelp();
|
|
454
|
+
process.exit(0);
|
|
455
|
+
}
|
|
456
|
+
if (hasFlag(args, "v", "version")) {
|
|
457
|
+
console.log(VERSION);
|
|
458
|
+
process.exit(0);
|
|
459
|
+
}
|
|
460
|
+
const positionalArgs = args.filter((a) => !a.startsWith("--") && !a.startsWith("-"));
|
|
344
461
|
const projectName = positionalArgs[0];
|
|
345
462
|
if (!projectName) {
|
|
346
|
-
|
|
347
|
-
"Usage: create-theokit <project-name> [--template=name] [--bare] [--skip-install]"
|
|
348
|
-
);
|
|
349
|
-
console.error("");
|
|
350
|
-
console.error("Templates: default, dashboard, api-only, postgres, saas");
|
|
351
|
-
console.error("");
|
|
352
|
-
console.error("Recipes:");
|
|
353
|
-
console.error(
|
|
354
|
-
" npx create-theokit my-app Full TheoUI + agent surface (requires @theokit/sdk on npm)"
|
|
355
|
-
);
|
|
356
|
-
console.error(
|
|
357
|
-
" npx create-theokit my-app --bare Minimal Hello Theo (no @theokit/* deps \u2014 always works)"
|
|
358
|
-
);
|
|
359
|
-
console.error(" npx create-theokit my-app --template=dashboard");
|
|
360
|
-
console.error(
|
|
361
|
-
" npx create-theokit my-app --skip-install Scaffold files only, run install manually"
|
|
362
|
-
);
|
|
463
|
+
showHelp();
|
|
363
464
|
process.exit(1);
|
|
364
465
|
}
|
|
365
|
-
const
|
|
366
|
-
const
|
|
367
|
-
const
|
|
368
|
-
const
|
|
466
|
+
const templateName = getFlag(args, "template") ?? "default";
|
|
467
|
+
const bare = hasFlag(args, "bare");
|
|
468
|
+
const skipInstall = hasFlag(args, "skip-install");
|
|
469
|
+
const useDefaults = hasFlag(args, "yes");
|
|
470
|
+
const disableGit = hasFlag(args, "disable-git");
|
|
471
|
+
const useBiome = hasFlag(args, "biome");
|
|
472
|
+
const exampleFlag = getFlag(args, "example");
|
|
473
|
+
const importAlias = getFlag(args, "import-alias") ?? "@/*";
|
|
474
|
+
let pkgManagerOverride;
|
|
475
|
+
if (hasFlag(args, "use-npm")) pkgManagerOverride = "npm";
|
|
476
|
+
else if (hasFlag(args, "use-pnpm")) pkgManagerOverride = "pnpm";
|
|
477
|
+
else if (hasFlag(args, "use-yarn")) pkgManagerOverride = "yarn";
|
|
478
|
+
else if (hasFlag(args, "use-bun")) pkgManagerOverride = "bun";
|
|
369
479
|
let backends = [];
|
|
370
480
|
try {
|
|
371
481
|
backends = parseBackendFlags(args);
|
|
@@ -375,8 +485,42 @@ function main() {
|
|
|
375
485
|
process.exit(1);
|
|
376
486
|
}
|
|
377
487
|
const targetDir = resolve3(process.cwd(), projectName);
|
|
488
|
+
if (exampleFlag) {
|
|
489
|
+
try {
|
|
490
|
+
console.log(`
|
|
491
|
+
Cloning example "${exampleFlag}" into ${projectName}...
|
|
492
|
+
`);
|
|
493
|
+
cloneExample(exampleFlag, targetDir);
|
|
494
|
+
const pkgManager = pkgManagerOverride ?? detectPkgManager();
|
|
495
|
+
if (!skipInstall) {
|
|
496
|
+
console.log(`Installing dependencies with ${pkgManager}...
|
|
497
|
+
`);
|
|
498
|
+
runInstall(targetDir, pkgManager);
|
|
499
|
+
}
|
|
500
|
+
if (!disableGit) initGit(targetDir);
|
|
501
|
+
console.log(`
|
|
502
|
+
\u2713 Example cloned to ${targetDir}
|
|
503
|
+
`);
|
|
504
|
+
console.log(` cd ${projectName}`);
|
|
505
|
+
console.log(` ${pkgManager === "npm" ? "npm run" : pkgManager} dev
|
|
506
|
+
`);
|
|
507
|
+
process.exit(0);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
console.error(`
|
|
510
|
+
\u2717 ${err.message}
|
|
511
|
+
`);
|
|
512
|
+
process.exit(1);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
let options;
|
|
516
|
+
if (useDefaults || bare) {
|
|
517
|
+
options = getDefaults(projectName);
|
|
518
|
+
if (useBiome) options.eslint = false;
|
|
519
|
+
} else {
|
|
520
|
+
options = await runPrompts(projectName);
|
|
521
|
+
}
|
|
378
522
|
try {
|
|
379
|
-
const suffix = bare ? " [--bare
|
|
523
|
+
const suffix = bare ? " [--bare]" : "";
|
|
380
524
|
const backendsSuffix = backends.length > 0 ? ` [+services: ${backends.join(", ")}]` : "";
|
|
381
525
|
console.log(
|
|
382
526
|
`
|
|
@@ -384,12 +528,13 @@ Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${
|
|
|
384
528
|
`
|
|
385
529
|
);
|
|
386
530
|
scaffold(targetDir, projectName, templateName, { bare });
|
|
531
|
+
applyOptions(targetDir, options, { importAlias, useBiome });
|
|
387
532
|
if (backends.length > 0) {
|
|
388
533
|
scaffoldServices({ targetDir, projectName, backends });
|
|
389
534
|
console.log(` \u2713 Scaffolded ${String(backends.length)} service(s): ${backends.join(", ")}
|
|
390
535
|
`);
|
|
391
536
|
}
|
|
392
|
-
const pkgManager = detectPkgManager();
|
|
537
|
+
const pkgManager = pkgManagerOverride ?? detectPkgManager();
|
|
393
538
|
if (skipInstall) {
|
|
394
539
|
console.log(`Skipping install (--skip-install). Run \`${pkgManager} install\` manually.
|
|
395
540
|
`);
|
|
@@ -398,16 +543,14 @@ Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${
|
|
|
398
543
|
`);
|
|
399
544
|
runInstall(targetDir, pkgManager);
|
|
400
545
|
}
|
|
546
|
+
if (!disableGit) initGit(targetDir);
|
|
401
547
|
console.log(`
|
|
402
548
|
\u2713 Project created at ${targetDir}
|
|
403
549
|
`);
|
|
404
|
-
console.log(
|
|
405
|
-
`);
|
|
550
|
+
console.log(" Next steps:\n");
|
|
406
551
|
console.log(` cd ${projectName}`);
|
|
407
|
-
if (skipInstall) {
|
|
408
|
-
|
|
409
|
-
}
|
|
410
|
-
console.log(` ${pkgManager === "npm" ? "npx" : pkgManager} theokit dev
|
|
552
|
+
if (skipInstall) console.log(` ${pkgManager} install`);
|
|
553
|
+
console.log(` ${pkgManager === "npm" ? "npm run" : pkgManager} dev
|
|
411
554
|
`);
|
|
412
555
|
} catch (err) {
|
|
413
556
|
console.error(`
|
|
@@ -416,7 +559,127 @@ Creating TheoKit project "${projectName}" (template: ${templateName})${suffix}${
|
|
|
416
559
|
process.exit(1);
|
|
417
560
|
}
|
|
418
561
|
}
|
|
419
|
-
|
|
562
|
+
function applyOptions(targetDir, options, opts) {
|
|
563
|
+
const pkgPath = resolve3(targetDir, "package.json");
|
|
564
|
+
if (opts.useBiome) {
|
|
565
|
+
const eslintPath = resolve3(targetDir, "eslint.config.mjs");
|
|
566
|
+
if (existsSync4(eslintPath)) unlinkSync4(eslintPath);
|
|
567
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
568
|
+
delete pkg.devDependencies.eslint;
|
|
569
|
+
delete pkg.devDependencies["eslint-config-prettier"];
|
|
570
|
+
delete pkg.devDependencies["typescript-eslint"];
|
|
571
|
+
pkg.devDependencies["@biomejs/biome"] = "^1.9.0";
|
|
572
|
+
pkg.scripts.lint = "biome check .";
|
|
573
|
+
pkg.scripts["lint:fix"] = "biome check . --fix";
|
|
574
|
+
pkg.scripts.format = "biome format . --write";
|
|
575
|
+
pkg.scripts["format:check"] = "biome format .";
|
|
576
|
+
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
577
|
+
writeFileSync4(
|
|
578
|
+
resolve3(targetDir, "biome.json"),
|
|
579
|
+
JSON.stringify(
|
|
580
|
+
{
|
|
581
|
+
$schema: "https://biomejs.dev/schemas/1.9.0/schema.json",
|
|
582
|
+
organizeImports: { enabled: true },
|
|
583
|
+
linter: { enabled: true, rules: { recommended: true } },
|
|
584
|
+
formatter: { enabled: true, indentStyle: "space", indentWidth: 2, lineWidth: 100 }
|
|
585
|
+
},
|
|
586
|
+
null,
|
|
587
|
+
2
|
|
588
|
+
) + "\n"
|
|
589
|
+
);
|
|
590
|
+
} else if (!options.eslint) {
|
|
591
|
+
const eslintPath = resolve3(targetDir, "eslint.config.mjs");
|
|
592
|
+
if (existsSync4(eslintPath)) unlinkSync4(eslintPath);
|
|
593
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
594
|
+
delete pkg.scripts.lint;
|
|
595
|
+
delete pkg.scripts["lint:fix"];
|
|
596
|
+
delete pkg.devDependencies.eslint;
|
|
597
|
+
delete pkg.devDependencies["eslint-config-prettier"];
|
|
598
|
+
delete pkg.devDependencies["typescript-eslint"];
|
|
599
|
+
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
600
|
+
}
|
|
601
|
+
if (options.tailwind) {
|
|
602
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
603
|
+
pkg.devDependencies = pkg.devDependencies ?? {};
|
|
604
|
+
pkg.devDependencies.tailwindcss = "^4.0.0";
|
|
605
|
+
pkg.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
606
|
+
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
607
|
+
const cssDir = existsSync4(resolve3(targetDir, "src/app")) ? "src/app" : "app";
|
|
608
|
+
writeFileSync4(resolve3(targetDir, `${cssDir}/globals.css`), '@import "tailwindcss";\n');
|
|
609
|
+
}
|
|
610
|
+
if (options.srcDir) {
|
|
611
|
+
const srcDir = resolve3(targetDir, "src");
|
|
612
|
+
mkdirSync(srcDir, { recursive: true });
|
|
613
|
+
for (const dir of ["app", "server"]) {
|
|
614
|
+
const from = resolve3(targetDir, dir);
|
|
615
|
+
const to = resolve3(srcDir, dir);
|
|
616
|
+
if (existsSync4(from)) renameSync2(from, to);
|
|
617
|
+
}
|
|
618
|
+
const appFrom = resolve3(targetDir, "app.ts");
|
|
619
|
+
if (existsSync4(appFrom)) renameSync2(appFrom, resolve3(srcDir, "app.ts"));
|
|
620
|
+
const tscPath2 = resolve3(targetDir, "tsconfig.json");
|
|
621
|
+
const tsc2 = JSON.parse(readFileSync4(tscPath2, "utf-8"));
|
|
622
|
+
tsc2.compilerOptions.baseUrl = "src";
|
|
623
|
+
tsc2.include = ["src/**/*.ts", "src/**/*.tsx"];
|
|
624
|
+
writeFileSync4(tscPath2, JSON.stringify(tsc2, null, 2) + "\n");
|
|
625
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
626
|
+
pkg.scripts.dev = "npx tsx --watch src/app.ts";
|
|
627
|
+
pkg.scripts["dev:bun"] = "bun --watch src/app.ts";
|
|
628
|
+
pkg.scripts.build = "npx tsup src/app.ts --format esm --out-dir dist";
|
|
629
|
+
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
630
|
+
}
|
|
631
|
+
const tscPath = resolve3(targetDir, "tsconfig.json");
|
|
632
|
+
const tsc = JSON.parse(readFileSync4(tscPath, "utf-8"));
|
|
633
|
+
if (!options.aliases) {
|
|
634
|
+
delete tsc.compilerOptions.baseUrl;
|
|
635
|
+
delete tsc.compilerOptions.paths;
|
|
636
|
+
} else if (opts.importAlias !== "@/*") {
|
|
637
|
+
const prefix = opts.importAlias.replace("/*", "");
|
|
638
|
+
tsc.compilerOptions.paths = {
|
|
639
|
+
[`${prefix}/*`]: ["./*"],
|
|
640
|
+
[`${prefix}/server/*`]: ["./server/*"],
|
|
641
|
+
[`${prefix}/app/*`]: ["./app/*"]
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
writeFileSync4(tscPath, JSON.stringify(tsc, null, 2) + "\n");
|
|
645
|
+
if (!options.agentsMd) {
|
|
646
|
+
const agentsPath = resolve3(targetDir, "AGENTS.md");
|
|
647
|
+
if (existsSync4(agentsPath)) unlinkSync4(agentsPath);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function initGit(targetDir) {
|
|
651
|
+
try {
|
|
652
|
+
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
653
|
+
execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
654
|
+
execSync('git commit -m "Initial commit from create-theokit" --no-verify', {
|
|
655
|
+
cwd: targetDir,
|
|
656
|
+
stdio: "ignore"
|
|
657
|
+
});
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function cloneExample(example, targetDir) {
|
|
662
|
+
const isUrl = example.startsWith("http://") || example.startsWith("https://");
|
|
663
|
+
const repo = isUrl ? example : `https://github.com/usetheodev/theokit-examples/tree/main/${example}`;
|
|
664
|
+
if (isUrl) {
|
|
665
|
+
execSync(`git clone --depth 1 ${repo} ${targetDir}`, { stdio: "inherit" });
|
|
666
|
+
rmSync2(join4(targetDir, ".git"), { recursive: true, force: true });
|
|
667
|
+
} else {
|
|
668
|
+
try {
|
|
669
|
+
execSync(`npx --yes degit usetheodev/theokit-examples/${example} ${targetDir}`, {
|
|
670
|
+
stdio: "inherit"
|
|
671
|
+
});
|
|
672
|
+
} catch {
|
|
673
|
+
throw new Error(
|
|
674
|
+
`Example "${example}" not found. Browse available examples at https://github.com/usetheodev/theokit-examples`
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
main().catch((err) => {
|
|
680
|
+
console.error(err);
|
|
681
|
+
process.exit(1);
|
|
682
|
+
});
|
|
420
683
|
export {
|
|
421
684
|
main
|
|
422
685
|
};
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/install.ts","../src/pkg-manager.ts","../src/preflight-node.ts","../src/scaffold-services.ts","../src/index.ts","../src/bare-transform.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport { runInstall } from './install.js'\nimport { detectPkgManager } from './pkg-manager.js'\nimport { assertNodeVersion } from './preflight-node.js'\nimport { parseBackendFlags, scaffoldServices, type BackendKind } from './scaffold-services.js'\n\nimport { scaffold } from './index.js'\n\nexport function main(): void {\n // Preflight FIRST — refuses to write any files if Node is below the SDK floor.\n // SDK requires Node ≥ 22.12; cryptic node:sqlite ABI errors otherwise.\n try {\n assertNodeVersion(process.version)\n } catch (err) {\n console.error('')\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n\n const args = process.argv.slice(2)\n const positionalArgs = args.filter((a) => !a.startsWith('--'))\n const projectName = positionalArgs[0]\n\n if (!projectName) {\n console.error(\n 'Usage: create-theokit <project-name> [--template=name] [--bare] [--skip-install]',\n )\n console.error('')\n console.error('Templates: default, dashboard, api-only, postgres, saas')\n console.error('')\n console.error('Recipes:')\n console.error(\n ' npx create-theokit my-app Full TheoUI + agent surface (requires @theokit/sdk on npm)',\n )\n console.error(\n ' npx create-theokit my-app --bare Minimal Hello Theo (no @theokit/* deps — always works)',\n )\n console.error(' npx create-theokit my-app --template=dashboard')\n console.error(\n ' npx create-theokit my-app --skip-install Scaffold files only, run install manually',\n )\n process.exit(1)\n }\n\n // Parse --template flag\n const templateFlag = args.find((a) => a.startsWith('--template='))\n const templateName = templateFlag ? templateFlag.split('=')[1] : 'default'\n\n // Parse --bare flag (only applies to default template)\n const bare = args.includes('--bare')\n\n // Parse --skip-install — useful for smoke testing, monorepo dogfood, air-gapped envs.\n const skipInstall = args.includes('--skip-install')\n\n // Parse --backend python | --backend node (Wave 2; multi-value supported)\n let backends: BackendKind[] = []\n try {\n backends = parseBackendFlags(args)\n } catch (err) {\n console.error('')\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n\n const targetDir = resolve(process.cwd(), projectName)\n\n try {\n const suffix = bare ? ' [--bare: skipping TheoUI defaults]' : ''\n const backendsSuffix = backends.length > 0 ? ` [+services: ${backends.join(', ')}]` : ''\n console.log(\n `\\nCreating TheoKit project \"${projectName}\" (template: ${templateName})${suffix}${backendsSuffix}...\\n`,\n )\n\n scaffold(targetDir, projectName, templateName, { bare })\n\n // Wave 2 — scaffold polyglot services AFTER main scaffold\n if (backends.length > 0) {\n scaffoldServices({ targetDir, projectName, backends })\n console.log(` ✓ Scaffolded ${String(backends.length)} service(s): ${backends.join(', ')}\\n`)\n }\n\n const pkgManager = detectPkgManager()\n if (skipInstall) {\n console.log(`Skipping install (--skip-install). Run \\`${pkgManager} install\\` manually.\\n`)\n } else {\n console.log(`Installing dependencies with ${pkgManager}...\\n`)\n runInstall(targetDir, pkgManager)\n }\n\n console.log(`\\n ✓ Project created at ${targetDir}\\n`)\n console.log(` Next steps:\\n`)\n console.log(` cd ${projectName}`)\n if (skipInstall) {\n console.log(` ${pkgManager} install`)\n }\n console.log(` ${pkgManager === 'npm' ? 'npx' : pkgManager} theokit dev\\n`)\n } catch (err) {\n console.error(`\\n ✗ ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n\n// Auto-execute when run as script\nmain()\n","import spawn from 'cross-spawn'\n\nimport type { PkgManager } from './pkg-manager.js'\n\nexport function runInstall(cwd: string, pkgManager: PkgManager): void {\n const result = spawn.sync(pkgManager, ['install'], {\n cwd,\n stdio: 'inherit',\n env: { ...process.env, NODE_ENV: 'development' },\n })\n\n if (result.status !== 0) {\n throw new Error(`Failed to install dependencies with ${pkgManager}`)\n }\n}\n","export type PkgManager = 'npm' | 'pnpm' | 'yarn' | 'bun'\n\nexport function detectPkgManager(): PkgManager {\n const ua = process.env.npm_config_user_agent ?? ''\n if (ua.startsWith('yarn')) return 'yarn'\n if (ua.startsWith('pnpm')) return 'pnpm'\n if (ua.startsWith('bun')) return 'bun'\n return 'npm'\n}\n","/**\n * Node version preflight for `create-theokit`.\n *\n * `@theokit/sdk` declares `engines.node: \">=22.12.0\"`. Users on older Node\n * hit cryptic `node:sqlite` / `better-sqlite3` ABI errors mid-chat without\n * any actionable diagnostic. This preflight runs at scaffold start, prints\n * a clear error, and exits before any FS write (ADR D4 of the plan).\n *\n * Zero-dep semver comparator — no `semver` package needed.\n */\n\n/** Minimum Node version required by `@theokit/sdk`. */\nexport const MIN_NODE_VERSION = '22.12.0'\n\n/**\n * Compare two semver strings (major.minor.patch). Strips leading `v` from\n * either side. Pre-release suffixes (e.g., `-rc.1`, `-nightly...`) are\n * parsed as the base patch — acceptable for a preflight (we're not gating\n * on exact pre-release ordering).\n *\n * Returns:\n * <0 when a < b\n * 0 when a == b\n * >0 when a > b\n */\nexport function compareSemver(a: string, b: string): number {\n const parse = (raw: string): [number, number, number] => {\n const stripped = raw.replace(/^v/, '').split('-')[0] ?? '0.0.0'\n const parts = stripped.split('.').map((s) => Number.parseInt(s, 10) || 0)\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0]\n }\n const [aMaj, aMin, aPat] = parse(a)\n const [bMaj, bMin, bPat] = parse(b)\n if (aMaj !== bMaj) return aMaj - bMaj\n if (aMin !== bMin) return aMin - bMin\n return aPat - bPat\n}\n\n/**\n * Throw with an actionable message if `currentRaw` is below `minimum`.\n * Default minimum is `MIN_NODE_VERSION` (22.12.0).\n *\n * Called as the first line of `create-theokit`'s `main` so the preflight\n * fires before any directory is written.\n */\nexport function assertNodeVersion(currentRaw: string, minimum: string = MIN_NODE_VERSION): void {\n const current = currentRaw.replace(/^v/, '')\n if (compareSemver(currentRaw, minimum) < 0) {\n throw new Error(\n `create-theokit requires Node ${minimum} or later (the @theokit/sdk peer engines floor).\\n` +\n ` Detected: Node ${current}\\n` +\n ` Fix: nvm install 22 && nvm use 22\\n` +\n ` (or your version manager equivalent — fnm, volta, asdf, nvs)\\n`,\n )\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scaffold helper. All write paths derived from the trusted\n * targetDir (CLI argument, resolved absolute). Read paths are the\n * bundled service templates shipped with this package.\n */\n/**\n * Phase 4 — `--backend python|node` scaffolding (T4.1, T4.2).\n *\n * After the main TheoKit scaffold runs, this helper:\n * - Copies the requested service template(s) under `<target>/services/<name>/`\n * - Substitutes `{{name}}` in `.tmpl` files\n * - Renames `.tmpl` files to drop the suffix\n * - Injects services config into the user's `theo.config.ts`\n * - Injects `@hey-api/client-fetch` into the user's package.json (EC-10)\n */\nimport {\n cpSync,\n existsSync,\n readFileSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n} from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport type BackendKind = 'python' | 'node'\n\nconst VALID_BACKENDS = ['python', 'node'] as const\n\n/**\n * Parse `--backend python` / `--backend node` (multi-value) from argv.\n *\n * Accepts:\n * --backend python\n * --backend=python\n * --backend python --backend node\n *\n * Throws on unknown backend name.\n */\nexport function parseBackendFlags(args: string[]): BackendKind[] {\n const out: BackendKind[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i] ?? ''\n let value: string | undefined\n if (a === '--backend') {\n value = args[i + 1]\n i++\n } else if (a.startsWith('--backend=')) {\n value = a.slice('--backend='.length)\n }\n if (value === undefined) continue\n if (!(VALID_BACKENDS as readonly string[]).includes(value)) {\n throw new Error(\n `unknown --backend value: '${value}'. Valid options: ${VALID_BACKENDS.join(', ')}.`,\n )\n }\n out.push(value as BackendKind)\n }\n return out\n}\n\nconst BACKEND_CONFIG: Record<\n BackendKind,\n {\n templateDir: string\n serviceName: string\n port: number\n proxy: string\n dev: string\n start: string\n }\n> = {\n python: {\n templateDir: 'agent-python',\n serviceName: 'agent',\n port: 8001,\n proxy: '/api/agent',\n dev: 'uvicorn main:app --reload --port 8001',\n start: 'uvicorn main:app --port 8001 --workers 4',\n },\n node: {\n templateDir: 'agent-node',\n serviceName: 'worker',\n port: 8002,\n proxy: '/api/worker',\n dev: 'pnpm dev',\n start: 'pnpm start',\n },\n}\n\nfunction getServiceTemplateDir(kind: BackendKind): string {\n return resolve(__dirname, '../templates/services', BACKEND_CONFIG[kind].templateDir)\n}\n\nfunction substituteTmpls(dir: string, projectName: string): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const stat = statSync(full)\n if (stat.isDirectory()) {\n substituteTmpls(full, projectName)\n continue\n }\n if (entry.endsWith('.tmpl')) {\n const content = readFileSync(full, 'utf-8').replace(/\\{\\{name\\}\\}/g, projectName)\n const dest = full.replace(/\\.tmpl$/, '')\n writeFileSync(dest, content)\n unlinkSync(full)\n }\n }\n}\n\ninterface ServiceEntry {\n runtime: BackendKind\n port: number\n proxy: string\n dev: string\n start: string\n}\n\n/**\n * Build the `services: {}` snippet to inject into `theo.config.ts`.\n * Returns the inner record literal — caller wraps in `services: { ... }`.\n */\nexport function buildServicesSnippet(selections: { name: string; entry: ServiceEntry }[]): string {\n if (selections.length === 0) return ''\n const blocks = selections.map(({ name, entry }) => {\n return ` ${name}: {\n runtime: '${entry.runtime}',\n port: ${String(entry.port)},\n proxy: '${entry.proxy}',\n dev: ${JSON.stringify(entry.dev)},\n start: ${JSON.stringify(entry.start)},\n },`\n })\n return ` services: {\\n${blocks.join('\\n')}\\n },\\n`\n}\n\n/**\n * Insert the services block into an existing `theo.config.ts`.\n * Strategy: find `defineConfig({` and append the services block before the closing brace.\n */\nexport function injectServicesIntoConfig(configSource: string, snippet: string): string {\n if (snippet.length === 0) return configSource\n if (configSource.includes('services:')) return configSource // already present\n\n // Match the LAST `}` before the closing of defineConfig({...})\n const re = /defineConfig\\(\\{([\\s\\S]*?)\\}\\)/m\n const match = re.exec(configSource)\n if (!match) return configSource\n\n const inner = match[1]\n // Strip trailing whitespace without backtracking-prone \\s+$ regex.\n let trimEnd = inner.length\n while (trimEnd > 0 && /\\s/.test(inner.charAt(trimEnd - 1))) {\n trimEnd--\n }\n const trimmed = inner.slice(0, trimEnd)\n const sep = trimmed.length > 0 && !trimmed.endsWith(',') ? ',\\n' : '\\n'\n const newInner = `${trimmed}${sep}${snippet}`\n return configSource.replace(re, `defineConfig({${newInner}})`)\n}\n\nexport function injectHeyApiDep(packageJsonSource: string): string {\n const pkg = JSON.parse(packageJsonSource) as {\n dependencies?: Record<string, string>\n }\n pkg.dependencies = pkg.dependencies ?? {}\n if (!('@hey-api/client-fetch' in pkg.dependencies)) {\n pkg.dependencies['@hey-api/client-fetch'] = '^0.6.0'\n }\n return JSON.stringify(pkg, null, 2) + '\\n'\n}\n\nexport interface ScaffoldServicesOptions {\n /** Target project directory (already scaffolded with the TS template). */\n targetDir: string\n /** Project name (substituted into .tmpl files). */\n projectName: string\n /** Which backends to scaffold. */\n backends: BackendKind[]\n}\n\nexport function scaffoldServices(options: ScaffoldServicesOptions): void {\n if (options.backends.length === 0) return\n\n const selections: { name: string; entry: ServiceEntry }[] = []\n\n for (const kind of options.backends) {\n const cfg = BACKEND_CONFIG[kind]\n const src = getServiceTemplateDir(kind)\n // Schema contract: dev/start commands run from `services/<serviceName>/` cwd\n // (services/schema/schema.ts line 35). The destination MUST equal serviceName\n // so the orchestrator's cwd resolution matches the on-disk directory.\n const dest = join(options.targetDir, 'services', cfg.serviceName)\n if (!existsSync(src)) {\n throw new Error(`service template not found: ${src}`)\n }\n cpSync(src, dest, { recursive: true })\n substituteTmpls(dest, options.projectName)\n\n selections.push({\n name: cfg.serviceName,\n entry: {\n runtime: kind,\n port: cfg.port,\n proxy: cfg.proxy,\n dev: cfg.dev,\n start: cfg.start,\n },\n })\n }\n\n // Inject services into theo.config.ts\n const configPath = join(options.targetDir, 'theo.config.ts')\n if (existsSync(configPath)) {\n const cfgSrc = readFileSync(configPath, 'utf-8')\n const snippet = buildServicesSnippet(selections)\n const updated = injectServicesIntoConfig(cfgSrc, snippet)\n if (updated !== cfgSrc) {\n writeFileSync(configPath, updated)\n }\n }\n\n // EC-10: inject @hey-api/client-fetch into user's package.json\n const pkgPath = join(options.targetDir, 'package.json')\n if (existsSync(pkgPath)) {\n const pkgSrc = readFileSync(pkgPath, 'utf-8')\n const updated = injectHeyApiDep(pkgSrc)\n writeFileSync(pkgPath, updated)\n }\n\n // Rename .gitignore for services if needed (none currently shipped, but reserved hook)\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * `create-theokit` scaffold tool. All write paths are derived from the\n * user-supplied target directory (CLI argument, resolved to absolute).\n * Read paths are the bundled `templates/` shipped with this package.\n * Build-time tool — no HTTP input.\n */\nimport {\n existsSync,\n cpSync,\n readFileSync,\n writeFileSync,\n renameSync,\n unlinkSync,\n readdirSync,\n rmSync,\n statSync,\n} from 'node:fs'\nimport { resolve, join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { applyBareTransform } from './bare-transform.js'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nfunction getTemplateDir(templateName = 'default'): string {\n return resolve(__dirname, '../templates', templateName)\n}\n\nfunction isValidProjectName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/.test(name)\n}\n\nexport interface ScaffoldOptions {\n bare?: boolean\n /** Test-only — force the bare transform to throw to validate EC-4 rollback. */\n _testForceTransformError?: string\n}\n\n// eslint-disable-next-line complexity -- scaffold orchestrator: validate → copy → rename _gitignore → template-substitute all *.tmpl → optional --bare transform with rollback. Branches are linear, not nested.\nexport function scaffold(\n targetDir: string,\n projectName: string,\n templateName = 'default',\n options: ScaffoldOptions = {},\n): void {\n // EC-4 + ADR D5: --bare only applies to default template\n if (options.bare && templateName !== 'default') {\n throw new Error(\n `--bare flag only applies to the default template; got \"${templateName}\". ` +\n `Use \\`npx create-theokit <name> --template=default --bare\\` or pick a different template.`,\n )\n }\n\n const templateDir = getTemplateDir(templateName)\n\n if (!existsSync(templateDir)) {\n throw new Error(\n `Template \"${templateName}\" not found. Available templates: default, dashboard, api-only, postgres, saas`,\n )\n }\n\n // Bug #1 fix: \".\" means current directory — use dir basename as project name\n const resolvedName = projectName === '.'\n ? resolve(targetDir).split('/').pop() ?? 'my-app'\n : projectName\n\n if (!isValidProjectName(resolvedName)) {\n throw new Error(\n `Invalid project name \"${resolvedName}\". ` +\n `Use lowercase letters, numbers, hyphens, and dots. Must start with a letter or number.`,\n )\n }\n\n if (existsSync(targetDir)) {\n const contents = readdirSync(targetDir)\n if (contents.length > 0) {\n throw new Error(`Directory \"${targetDir}\" is not empty. Please use an empty directory.`)\n }\n }\n\n cpSync(templateDir, targetDir, { recursive: true })\n\n const gitignoreSrc = join(targetDir, '_gitignore')\n const gitignoreDest = join(targetDir, '.gitignore')\n if (existsSync(gitignoreSrc)) {\n renameSync(gitignoreSrc, gitignoreDest)\n }\n\n // Apply {{name}} substitution to every `*.tmpl` file in the target dir.\n // Each `foo.tmpl` becomes `foo` with placeholders replaced. Walks only\n // the project root (deeper subfolders don't currently need templating).\n for (const entry of readdirSync(targetDir)) {\n if (!entry.endsWith('.tmpl')) continue\n const src = join(targetDir, entry)\n const stat = statSync(src)\n if (!stat.isFile()) continue\n const dst = join(targetDir, entry.slice(0, -'.tmpl'.length))\n const content = readFileSync(src, 'utf-8')\n const replaced = content.replace(/\\{\\{name\\}\\}/g, resolvedName)\n writeFileSync(dst, replaced)\n unlinkSync(src)\n }\n\n // T4.1 — Apply --bare transform with EC-4 atomic rollback\n if (options.bare) {\n try {\n applyBareTransform(targetDir, {\n _testForceError: options._testForceTransformError,\n })\n } catch (err) {\n // EC-4: roll back partial state\n rmSync(targetDir, { recursive: true, force: true })\n const original = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Scaffold rolled back: bare transform failed. Check filesystem perms.\\nOriginal error: ${original}`,\n )\n }\n }\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Scaffold transform. Mutates files inside the freshly-created target\n * directory whose absolute path is the function input. No HTTP input.\n */\n/**\n * T4.1 — `--bare` transformation.\n *\n * Applied AFTER the default template is copied. Removes:\n * - `@theokit/ui` from `package.json` dependencies (TheoUI bundled components)\n * - `@theokit/sdk` from `package.json` dependencies (agent SDK — see below)\n * - `app/page.tsx` agent-surface content (replaces with Hello Theo)\n * - `server/routes/chat.ts` (mock chat — depends on SDK + TheoUI events)\n * - `tailwind.config.ts` + `postcss.config.js` (Tailwind toolchain — only\n * needed by the @theokit/ui-driven default surface)\n * - tailwind* + postcss* from devDependencies (toolchain cleanup)\n *\n * Why SDK removal is in --bare:\n * `@theokit/sdk` is not yet on the public npm registry (operator-deferred\n * publish per macro roadmap item #3). A user running `npx create-theokit`\n * without `--bare` hits `npm install` → 404. The `--bare` path produces a\n * scaffold that ALWAYS works without registry dependencies — Hello Theo\n * with a clean structure to grow into.\n *\n * EC-4: callers MUST wrap this in try/catch + `rmSync` rollback so a partial\n * transform never leaves the target dir in a broken state.\n */\n\nimport { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\n\nconst HELLO_PAGE = `export default function Page() {\n return <h1>Hello Theo</h1>\n}\n`\n\nexport interface BareTransformOptions {\n /** Test-only — force a synthetic write failure to validate rollback path. */\n _testForceError?: string\n}\n\nexport function applyBareTransform(targetDir: string, options: BareTransformOptions = {}): void {\n if (options._testForceError) {\n throw new Error(`Forced transform failure: ${options._testForceError}`)\n }\n\n // 1. Remove @theokit/ui + @theokit/sdk + tailwind toolchain from deps\n const pkgPath = join(targetDir, 'package.json')\n if (existsSync(pkgPath)) {\n interface PartialPackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n [key: string]: unknown\n }\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PartialPackageJson\n if (pkg.dependencies) {\n delete pkg.dependencies['@theokit/ui']\n // Drop SDK — operator-deferred npm publish (macro roadmap item #3).\n // Without removal, `npm install` hits 404 for any consumer outside\n // the workspace.\n delete pkg.dependencies['@theokit/sdk']\n // lucide-react ships with the TheoUI surface; --bare doesn't render\n // any icons so it's safe to drop.\n delete pkg.dependencies['lucide-react']\n }\n if (pkg.devDependencies) {\n // Tailwind toolchain is only needed by the @theokit/ui-driven default\n // surface. --bare ships unstyled Hello Theo; no Tailwind required.\n delete pkg.devDependencies.tailwindcss\n delete pkg.devDependencies['tailwindcss-animate']\n delete pkg.devDependencies.postcss\n delete pkg.devDependencies.autoprefixer\n }\n writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\\n`)\n }\n\n // 2. Replace app/page.tsx with Hello Theo\n const pagePath = join(targetDir, 'app/page.tsx')\n if (existsSync(pagePath)) {\n writeFileSync(pagePath, HELLO_PAGE)\n }\n\n // 3. Remove mock chat route (depends on AgentEvent type and TheoUI deps)\n const chatPath = join(targetDir, 'server/routes/chat.ts')\n if (existsSync(chatPath)) {\n unlinkSync(chatPath)\n }\n\n // 4. Remove tailwind + postcss config files (toolchain dropped from devDeps)\n const tailwindCfg = join(targetDir, 'tailwind.config.ts')\n if (existsSync(tailwindCfg)) {\n unlinkSync(tailwindCfg)\n }\n const postcssCfg = join(targetDir, 'postcss.config.js')\n if (existsSync(postcssCfg)) {\n unlinkSync(postcssCfg)\n }\n}\n"],"mappings":";;;AAAA,SAAS,WAAAA,gBAAe;;;ACAxB,OAAO,WAAW;AAIX,SAAS,WAAW,KAAa,YAA8B;AACpE,QAAM,SAAS,MAAM,KAAK,YAAY,CAAC,SAAS,GAAG;AAAA,IACjD;AAAA,IACA,OAAO;AAAA,IACP,KAAK,EAAE,GAAG,QAAQ,KAAK,UAAU,cAAc;AAAA,EACjD,CAAC;AAED,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,uCAAuC,UAAU,EAAE;AAAA,EACrE;AACF;;;ACZO,SAAS,mBAA+B;AAC7C,QAAM,KAAK,QAAQ,IAAI,yBAAyB;AAChD,MAAI,GAAG,WAAW,MAAM,EAAG,QAAO;AAClC,MAAI,GAAG,WAAW,MAAM,EAAG,QAAO;AAClC,MAAI,GAAG,WAAW,KAAK,EAAG,QAAO;AACjC,SAAO;AACT;;;ACIO,IAAM,mBAAmB;AAazB,SAAS,cAAc,GAAW,GAAmB;AAC1D,QAAM,QAAQ,CAAC,QAA0C;AACvD,UAAM,WAAW,IAAI,QAAQ,MAAM,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC,KAAK;AACxD,UAAM,QAAQ,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,OAAO,SAAS,GAAG,EAAE,KAAK,CAAC;AACxE,WAAO,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;AAAA,EACrD;AACA,QAAM,CAAC,MAAM,MAAM,IAAI,IAAI,MAAM,CAAC;AAClC,QAAM,CAAC,MAAM,MAAM,IAAI,IAAI,MAAM,CAAC;AAClC,MAAI,SAAS,KAAM,QAAO,OAAO;AACjC,MAAI,SAAS,KAAM,QAAO,OAAO;AACjC,SAAO,OAAO;AAChB;AASO,SAAS,kBAAkB,YAAoB,UAAkB,kBAAwB;AAC9F,QAAM,UAAU,WAAW,QAAQ,MAAM,EAAE;AAC3C,MAAI,cAAc,YAAY,OAAO,IAAI,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,gCAAgC,OAAO;AAAA,mBACjB,OAAO;AAAA;AAAA;AAAA;AAAA,IAG/B;AAAA,EACF;AACF;;;ACxCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS,MAAM,eAAe;AACvC,SAAS,qBAAqB;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAIxD,IAAM,iBAAiB,CAAC,UAAU,MAAM;AAYjC,SAAS,kBAAkB,MAA+B;AAC/D,QAAM,MAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,IAAI,KAAK,CAAC,KAAK;AACrB,QAAI;AACJ,QAAI,MAAM,aAAa;AACrB,cAAQ,KAAK,IAAI,CAAC;AAClB;AAAA,IACF,WAAW,EAAE,WAAW,YAAY,GAAG;AACrC,cAAQ,EAAE,MAAM,aAAa,MAAM;AAAA,IACrC;AACA,QAAI,UAAU,OAAW;AACzB,QAAI,CAAE,eAAqC,SAAS,KAAK,GAAG;AAC1D,YAAM,IAAI;AAAA,QACR,6BAA6B,KAAK,qBAAqB,eAAe,KAAK,IAAI,CAAC;AAAA,MAClF;AAAA,IACF;AACA,QAAI,KAAK,KAAoB;AAAA,EAC/B;AACA,SAAO;AACT;AAEA,IAAM,iBAUF;AAAA,EACF,QAAQ;AAAA,IACN,aAAa;AAAA,IACb,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AAAA,EACA,MAAM;AAAA,IACJ,aAAa;AAAA,IACb,aAAa;AAAA,IACb,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AACF;AAEA,SAAS,sBAAsB,MAA2B;AACxD,SAAO,QAAQ,WAAW,yBAAyB,eAAe,IAAI,EAAE,WAAW;AACrF;AAEA,SAAS,gBAAgB,KAAa,aAA2B;AAC/D,aAAW,SAAS,YAAY,GAAG,GAAG;AACpC,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,UAAM,OAAO,SAAS,IAAI;AAC1B,QAAI,KAAK,YAAY,GAAG;AACtB,sBAAgB,MAAM,WAAW;AACjC;AAAA,IACF;AACA,QAAI,MAAM,SAAS,OAAO,GAAG;AAC3B,YAAM,UAAU,aAAa,MAAM,OAAO,EAAE,QAAQ,iBAAiB,WAAW;AAChF,YAAM,OAAO,KAAK,QAAQ,WAAW,EAAE;AACvC,oBAAc,MAAM,OAAO;AAC3B,iBAAW,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAcO,SAAS,qBAAqB,YAA6D;AAChG,MAAI,WAAW,WAAW,EAAG,QAAO;AACpC,QAAM,SAAS,WAAW,IAAI,CAAC,EAAE,MAAM,MAAM,MAAM;AACjD,WAAO,OAAO,IAAI;AAAA,kBACJ,MAAM,OAAO;AAAA,cACjB,OAAO,MAAM,IAAI,CAAC;AAAA,gBAChB,MAAM,KAAK;AAAA,aACd,KAAK,UAAU,MAAM,GAAG,CAAC;AAAA,eACvB,KAAK,UAAU,MAAM,KAAK,CAAC;AAAA;AAAA,EAExC,CAAC;AACD,SAAO;AAAA,EAAkB,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA;AAC5C;AAMO,SAAS,yBAAyB,cAAsB,SAAyB;AACtF,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,aAAa,SAAS,WAAW,EAAG,QAAO;AAG/C,QAAM,KAAK;AACX,QAAM,QAAQ,GAAG,KAAK,YAAY;AAClC,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,QAAQ,MAAM,CAAC;AAErB,MAAI,UAAU,MAAM;AACpB,SAAO,UAAU,KAAK,KAAK,KAAK,MAAM,OAAO,UAAU,CAAC,CAAC,GAAG;AAC1D;AAAA,EACF;AACA,QAAM,UAAU,MAAM,MAAM,GAAG,OAAO;AACtC,QAAM,MAAM,QAAQ,SAAS,KAAK,CAAC,QAAQ,SAAS,GAAG,IAAI,QAAQ;AACnE,QAAM,WAAW,GAAG,OAAO,GAAG,GAAG,GAAG,OAAO;AAC3C,SAAO,aAAa,QAAQ,IAAI,iBAAiB,QAAQ,IAAI;AAC/D;AAEO,SAAS,gBAAgB,mBAAmC;AACjE,QAAM,MAAM,KAAK,MAAM,iBAAiB;AAGxC,MAAI,eAAe,IAAI,gBAAgB,CAAC;AACxC,MAAI,EAAE,2BAA2B,IAAI,eAAe;AAClD,QAAI,aAAa,uBAAuB,IAAI;AAAA,EAC9C;AACA,SAAO,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI;AACxC;AAWO,SAAS,iBAAiB,SAAwC;AACvE,MAAI,QAAQ,SAAS,WAAW,EAAG;AAEnC,QAAM,aAAsD,CAAC;AAE7D,aAAW,QAAQ,QAAQ,UAAU;AACnC,UAAM,MAAM,eAAe,IAAI;AAC/B,UAAM,MAAM,sBAAsB,IAAI;AAItC,UAAM,OAAO,KAAK,QAAQ,WAAW,YAAY,IAAI,WAAW;AAChE,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAM,IAAI,MAAM,+BAA+B,GAAG,EAAE;AAAA,IACtD;AACA,WAAO,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC;AACrC,oBAAgB,MAAM,QAAQ,WAAW;AAEzC,eAAW,KAAK;AAAA,MACd,MAAM,IAAI;AAAA,MACV,OAAO;AAAA,QACL,SAAS;AAAA,QACT,MAAM,IAAI;AAAA,QACV,OAAO,IAAI;AAAA,QACX,KAAK,IAAI;AAAA,QACT,OAAO,IAAI;AAAA,MACb;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,aAAa,KAAK,QAAQ,WAAW,gBAAgB;AAC3D,MAAI,WAAW,UAAU,GAAG;AAC1B,UAAM,SAAS,aAAa,YAAY,OAAO;AAC/C,UAAM,UAAU,qBAAqB,UAAU;AAC/C,UAAM,UAAU,yBAAyB,QAAQ,OAAO;AACxD,QAAI,YAAY,QAAQ;AACtB,oBAAc,YAAY,OAAO;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,UAAU,KAAK,QAAQ,WAAW,cAAc;AACtD,MAAI,WAAW,OAAO,GAAG;AACvB,UAAM,SAAS,aAAa,SAAS,OAAO;AAC5C,UAAM,UAAU,gBAAgB,MAAM;AACtC,kBAAc,SAAS,OAAO;AAAA,EAChC;AAGF;;;ACtOA;AAAA,EACE,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA,cAAAC;AAAA,EACA,eAAAC;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,OACK;AACP,SAAS,WAAAC,UAAS,QAAAC,OAAM,WAAAC,gBAAe;AACvC,SAAS,iBAAAC,sBAAqB;;;ACS9B,SAAS,cAAAC,aAAY,gBAAAC,eAAc,iBAAAC,gBAAe,cAAAC,mBAAkB;AACpE,SAAS,QAAAC,aAAY;AAErB,IAAM,aAAa;AAAA;AAAA;AAAA;AAUZ,SAAS,mBAAmB,WAAmB,UAAgC,CAAC,GAAS;AAC9F,MAAI,QAAQ,iBAAiB;AAC3B,UAAM,IAAI,MAAM,6BAA6B,QAAQ,eAAe,EAAE;AAAA,EACxE;AAGA,QAAM,UAAUA,MAAK,WAAW,cAAc;AAC9C,MAAIJ,YAAW,OAAO,GAAG;AAMvB,UAAM,MAAM,KAAK,MAAMC,cAAa,SAAS,OAAO,CAAC;AACrD,QAAI,IAAI,cAAc;AACpB,aAAO,IAAI,aAAa,aAAa;AAIrC,aAAO,IAAI,aAAa,cAAc;AAGtC,aAAO,IAAI,aAAa,cAAc;AAAA,IACxC;AACA,QAAI,IAAI,iBAAiB;AAGvB,aAAO,IAAI,gBAAgB;AAC3B,aAAO,IAAI,gBAAgB,qBAAqB;AAChD,aAAO,IAAI,gBAAgB;AAC3B,aAAO,IAAI,gBAAgB;AAAA,IAC7B;AACA,IAAAC,eAAc,SAAS,GAAG,KAAK,UAAU,KAAK,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC5D;AAGA,QAAM,WAAWE,MAAK,WAAW,cAAc;AAC/C,MAAIJ,YAAW,QAAQ,GAAG;AACxB,IAAAE,eAAc,UAAU,UAAU;AAAA,EACpC;AAGA,QAAM,WAAWE,MAAK,WAAW,uBAAuB;AACxD,MAAIJ,YAAW,QAAQ,GAAG;AACxB,IAAAG,YAAW,QAAQ;AAAA,EACrB;AAGA,QAAM,cAAcC,MAAK,WAAW,oBAAoB;AACxD,MAAIJ,YAAW,WAAW,GAAG;AAC3B,IAAAG,YAAW,WAAW;AAAA,EACxB;AACA,QAAM,aAAaC,MAAK,WAAW,mBAAmB;AACtD,MAAIJ,YAAW,UAAU,GAAG;AAC1B,IAAAG,YAAW,UAAU;AAAA,EACvB;AACF;;;AD1EA,IAAME,aAAYC,SAAQC,eAAc,YAAY,GAAG,CAAC;AAExD,SAAS,eAAe,eAAe,WAAmB;AACxD,SAAOC,SAAQH,YAAW,gBAAgB,YAAY;AACxD;AAEA,SAAS,mBAAmB,MAAuB;AACjD,SAAO,yBAAyB,KAAK,IAAI;AAC3C;AASO,SAAS,SACd,WACA,aACA,eAAe,WACf,UAA2B,CAAC,GACtB;AAEN,MAAI,QAAQ,QAAQ,iBAAiB,WAAW;AAC9C,UAAM,IAAI;AAAA,MACR,0DAA0D,YAAY;AAAA,IAExE;AAAA,EACF;AAEA,QAAM,cAAc,eAAe,YAAY;AAE/C,MAAI,CAACI,YAAW,WAAW,GAAG;AAC5B,UAAM,IAAI;AAAA,MACR,aAAa,YAAY;AAAA,IAC3B;AAAA,EACF;AAGA,QAAM,eAAe,gBAAgB,MACjCD,SAAQ,SAAS,EAAE,MAAM,GAAG,EAAE,IAAI,KAAK,WACvC;AAEJ,MAAI,CAAC,mBAAmB,YAAY,GAAG;AACrC,UAAM,IAAI;AAAA,MACR,yBAAyB,YAAY;AAAA,IAEvC;AAAA,EACF;AAEA,MAAIC,YAAW,SAAS,GAAG;AACzB,UAAM,WAAWC,aAAY,SAAS;AACtC,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,IAAI,MAAM,cAAc,SAAS,gDAAgD;AAAA,IACzF;AAAA,EACF;AAEA,EAAAC,QAAO,aAAa,WAAW,EAAE,WAAW,KAAK,CAAC;AAElD,QAAM,eAAeC,MAAK,WAAW,YAAY;AACjD,QAAM,gBAAgBA,MAAK,WAAW,YAAY;AAClD,MAAIH,YAAW,YAAY,GAAG;AAC5B,eAAW,cAAc,aAAa;AAAA,EACxC;AAKA,aAAW,SAASC,aAAY,SAAS,GAAG;AAC1C,QAAI,CAAC,MAAM,SAAS,OAAO,EAAG;AAC9B,UAAM,MAAME,MAAK,WAAW,KAAK;AACjC,UAAM,OAAOC,UAAS,GAAG;AACzB,QAAI,CAAC,KAAK,OAAO,EAAG;AACpB,UAAM,MAAMD,MAAK,WAAW,MAAM,MAAM,GAAG,CAAC,QAAQ,MAAM,CAAC;AAC3D,UAAM,UAAUE,cAAa,KAAK,OAAO;AACzC,UAAM,WAAW,QAAQ,QAAQ,iBAAiB,YAAY;AAC9D,IAAAC,eAAc,KAAK,QAAQ;AAC3B,IAAAC,YAAW,GAAG;AAAA,EAChB;AAGA,MAAI,QAAQ,MAAM;AAChB,QAAI;AACF,yBAAmB,WAAW;AAAA,QAC5B,iBAAiB,QAAQ;AAAA,MAC3B,CAAC;AAAA,IACH,SAAS,KAAK;AAEZ,aAAO,WAAW,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAClD,YAAM,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAChE,YAAM,IAAI;AAAA,QACR;AAAA,kBAAyF,QAAQ;AAAA,MACnG;AAAA,IACF;AAAA,EACF;AACF;;;AL7GO,SAAS,OAAa;AAG3B,MAAI;AACF,sBAAkB,QAAQ,OAAO;AAAA,EACnC,SAAS,KAAK;AACZ,YAAQ,MAAM,EAAE;AAChB,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,iBAAiB,KAAK,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,IAAI,CAAC;AAC7D,QAAM,cAAc,eAAe,CAAC;AAEpC,MAAI,CAAC,aAAa;AAChB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,MAAM,EAAE;AAChB,YAAQ,MAAM,yDAAyD;AACvE,YAAQ,MAAM,EAAE;AAChB,YAAQ,MAAM,UAAU;AACxB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,MAAM,kDAAkD;AAChE,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,eAAe,KAAK,KAAK,CAAC,MAAM,EAAE,WAAW,aAAa,CAAC;AACjE,QAAM,eAAe,eAAe,aAAa,MAAM,GAAG,EAAE,CAAC,IAAI;AAGjE,QAAM,OAAO,KAAK,SAAS,QAAQ;AAGnC,QAAM,cAAc,KAAK,SAAS,gBAAgB;AAGlD,MAAI,WAA0B,CAAC;AAC/B,MAAI;AACF,eAAW,kBAAkB,IAAI;AAAA,EACnC,SAAS,KAAK;AACZ,YAAQ,MAAM,EAAE;AAChB,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAYC,SAAQ,QAAQ,IAAI,GAAG,WAAW;AAEpD,MAAI;AACF,UAAM,SAAS,OAAO,wCAAwC;AAC9D,UAAM,iBAAiB,SAAS,SAAS,IAAI,gBAAgB,SAAS,KAAK,IAAI,CAAC,MAAM;AACtF,YAAQ;AAAA,MACN;AAAA,4BAA+B,WAAW,gBAAgB,YAAY,IAAI,MAAM,GAAG,cAAc;AAAA;AAAA,IACnG;AAEA,aAAS,WAAW,aAAa,cAAc,EAAE,KAAK,CAAC;AAGvD,QAAI,SAAS,SAAS,GAAG;AACvB,uBAAiB,EAAE,WAAW,aAAa,SAAS,CAAC;AACrD,cAAQ,IAAI,uBAAkB,OAAO,SAAS,MAAM,CAAC,gBAAgB,SAAS,KAAK,IAAI,CAAC;AAAA,CAAI;AAAA,IAC9F;AAEA,UAAM,aAAa,iBAAiB;AACpC,QAAI,aAAa;AACf,cAAQ,IAAI,4CAA4C,UAAU;AAAA,CAAwB;AAAA,IAC5F,OAAO;AACL,cAAQ,IAAI,gCAAgC,UAAU;AAAA,CAAO;AAC7D,iBAAW,WAAW,UAAU;AAAA,IAClC;AAEA,YAAQ,IAAI;AAAA,8BAA4B,SAAS;AAAA,CAAI;AACrD,YAAQ,IAAI;AAAA,CAAiB;AAC7B,YAAQ,IAAI,UAAU,WAAW,EAAE;AACnC,QAAI,aAAa;AACf,cAAQ,IAAI,OAAO,UAAU,UAAU;AAAA,IACzC;AACA,YAAQ,IAAI,OAAO,eAAe,QAAQ,QAAQ,UAAU;AAAA,CAAgB;AAAA,EAC9E,SAAS,KAAK;AACZ,YAAQ,MAAM;AAAA,WAAU,IAAc,OAAO;AAAA,CAAI;AACjD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,KAAK;","names":["resolve","existsSync","cpSync","readFileSync","writeFileSync","unlinkSync","readdirSync","statSync","resolve","join","dirname","fileURLToPath","existsSync","readFileSync","writeFileSync","unlinkSync","join","__dirname","dirname","fileURLToPath","resolve","existsSync","readdirSync","cpSync","join","statSync","readFileSync","writeFileSync","unlinkSync","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/install.ts","../src/pkg-manager.ts","../src/preflight-node.ts","../src/prompts.ts","../src/scaffold-services.ts","../src/index.ts","../src/bare-transform.ts"],"sourcesContent":["import { execSync } from 'node:child_process'\nimport {\n readFileSync,\n writeFileSync,\n unlinkSync,\n mkdirSync,\n renameSync,\n existsSync,\n rmSync,\n} from 'node:fs'\nimport { resolve, join } from 'node:path'\n\nimport { runInstall } from './install.js'\nimport { detectPkgManager } from './pkg-manager.js'\nimport { assertNodeVersion } from './preflight-node.js'\nimport { runPrompts, getDefaults, type ProjectOptions } from './prompts.js'\nimport { parseBackendFlags, scaffoldServices, type BackendKind } from './scaffold-services.js'\n\nimport { scaffold } from './index.js'\n\nconst VERSION = '0.8.0'\n\nfunction getFlag(args: string[], name: string): string | undefined {\n const flag = args.find((a) => a.startsWith(`--${name}=`))\n return flag ? flag.split('=')[1] : undefined\n}\n\nfunction hasFlag(args: string[], ...names: string[]): boolean {\n return names.some((n) => args.includes(`--${n}`) || args.includes(`-${n}`))\n}\n\nfunction showHelp(): void {\n console.log(`\n create-theokit v${VERSION}\n\n Usage: create-theokit <project-name> [options]\n\n Options:\n -h, --help Show this help message\n -v, --version Show version number\n --yes Use recommended defaults (skip prompts)\n --template=<name> Template to use (default: \"default\")\n --bare Minimal app (no @theokit/* deps)\n --skip-install Scaffold files only, skip package install\n --disable-git Skip git init\n --use-npm Use npm as package manager\n --use-pnpm Use pnpm as package manager\n --use-yarn Use yarn as package manager\n --use-bun Use bun as package manager\n --import-alias=<alias> Import alias (default: \"@/*\")\n --example=<name|github-url> Bootstrap from a GitHub example\n --biome Use Biome instead of ESLint\n --agents-md Include AGENTS.md (default: true)\n\n Examples:\n npx create-theokit my-app --yes\n npx create-theokit my-app\n npx create-theokit my-app --bare --skip-install\n npx create-theokit my-app --use-bun --biome\n npx create-theokit my-app --example=https://github.com/user/repo\n npx create-theokit my-app --import-alias=\"~/*\"\n`)\n}\n\nexport async function main(): Promise<void> {\n try {\n assertNodeVersion(process.version)\n } catch (err) {\n console.error('')\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n\n const args = process.argv.slice(2)\n\n // -h / --help\n if (hasFlag(args, 'h', 'help')) {\n showHelp()\n process.exit(0)\n }\n\n // -v / --version\n if (hasFlag(args, 'v', 'version')) {\n console.log(VERSION)\n process.exit(0)\n }\n\n const positionalArgs = args.filter((a) => !a.startsWith('--') && !a.startsWith('-'))\n const projectName = positionalArgs[0]\n\n if (!projectName) {\n showHelp()\n process.exit(1)\n }\n\n // Parse flags\n const templateName = getFlag(args, 'template') ?? 'default'\n const bare = hasFlag(args, 'bare')\n const skipInstall = hasFlag(args, 'skip-install')\n const useDefaults = hasFlag(args, 'yes')\n const disableGit = hasFlag(args, 'disable-git')\n const useBiome = hasFlag(args, 'biome')\n const exampleFlag = getFlag(args, 'example')\n const importAlias = getFlag(args, 'import-alias') ?? '@/*'\n\n // --use-npm/pnpm/yarn/bun override\n let pkgManagerOverride: string | undefined\n if (hasFlag(args, 'use-npm')) pkgManagerOverride = 'npm'\n else if (hasFlag(args, 'use-pnpm')) pkgManagerOverride = 'pnpm'\n else if (hasFlag(args, 'use-yarn')) pkgManagerOverride = 'yarn'\n else if (hasFlag(args, 'use-bun')) pkgManagerOverride = 'bun'\n\n let backends: BackendKind[] = []\n try {\n backends = parseBackendFlags(args)\n } catch (err) {\n console.error('')\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n\n const targetDir = resolve(process.cwd(), projectName)\n\n // --example: clone from GitHub\n if (exampleFlag) {\n try {\n console.log(`\\nCloning example \"${exampleFlag}\" into ${projectName}...\\n`)\n cloneExample(exampleFlag, targetDir)\n const pkgManager = pkgManagerOverride ?? detectPkgManager()\n if (!skipInstall) {\n console.log(`Installing dependencies with ${pkgManager}...\\n`)\n runInstall(targetDir, pkgManager)\n }\n if (!disableGit) initGit(targetDir)\n console.log(`\\n ✓ Example cloned to ${targetDir}\\n`)\n console.log(` cd ${projectName}`)\n console.log(` ${pkgManager === 'npm' ? 'npm run' : pkgManager} dev\\n`)\n process.exit(0)\n } catch (err) {\n console.error(`\\n ✗ ${(err as Error).message}\\n`)\n process.exit(1)\n }\n }\n\n // Interactive prompts (skipped with --yes or --bare)\n let options: ProjectOptions\n if (useDefaults || bare) {\n options = getDefaults(projectName)\n if (useBiome) options.eslint = false // Biome replaces ESLint\n } else {\n options = await runPrompts(projectName)\n }\n\n try {\n const suffix = bare ? ' [--bare]' : ''\n const backendsSuffix = backends.length > 0 ? ` [+services: ${backends.join(', ')}]` : ''\n console.log(\n `\\nCreating TheoKit project \"${projectName}\" (template: ${templateName})${suffix}${backendsSuffix}...\\n`,\n )\n\n scaffold(targetDir, projectName, templateName, { bare })\n\n // Post-scaffold: apply user options\n applyOptions(targetDir, options, { importAlias, useBiome })\n\n if (backends.length > 0) {\n scaffoldServices({ targetDir, projectName, backends })\n console.log(` ✓ Scaffolded ${String(backends.length)} service(s): ${backends.join(', ')}\\n`)\n }\n\n const pkgManager = pkgManagerOverride ?? detectPkgManager()\n if (skipInstall) {\n console.log(`Skipping install (--skip-install). Run \\`${pkgManager} install\\` manually.\\n`)\n } else {\n console.log(`Installing dependencies with ${pkgManager}...\\n`)\n runInstall(targetDir, pkgManager)\n }\n\n // git init (unless --disable-git)\n if (!disableGit) initGit(targetDir)\n\n console.log(`\\n ✓ Project created at ${targetDir}\\n`)\n console.log(' Next steps:\\n')\n console.log(` cd ${projectName}`)\n if (skipInstall) console.log(` ${pkgManager} install`)\n console.log(` ${pkgManager === 'npm' ? 'npm run' : pkgManager} dev\\n`)\n } catch (err) {\n console.error(`\\n ✗ ${(err as Error).message}\\n`)\n process.exit(1)\n }\n}\n\n// ── Post-scaffold transforms ──\n\ninterface ApplyOpts {\n importAlias: string\n useBiome: boolean\n}\n\nfunction applyOptions(targetDir: string, options: ProjectOptions, opts: ApplyOpts): void {\n const pkgPath = resolve(targetDir, 'package.json')\n\n // Biome: replace ESLint with Biome\n if (opts.useBiome) {\n const eslintPath = resolve(targetDir, 'eslint.config.mjs')\n if (existsSync(eslintPath)) unlinkSync(eslintPath)\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n delete pkg.devDependencies.eslint\n delete pkg.devDependencies['eslint-config-prettier']\n delete pkg.devDependencies['typescript-eslint']\n pkg.devDependencies['@biomejs/biome'] = '^1.9.0'\n pkg.scripts.lint = 'biome check .'\n pkg.scripts['lint:fix'] = 'biome check . --fix'\n pkg.scripts.format = 'biome format . --write'\n pkg.scripts['format:check'] = 'biome format .'\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n\n // Write biome.json\n writeFileSync(\n resolve(targetDir, 'biome.json'),\n JSON.stringify(\n {\n $schema: 'https://biomejs.dev/schemas/1.9.0/schema.json',\n organizeImports: { enabled: true },\n linter: { enabled: true, rules: { recommended: true } },\n formatter: { enabled: true, indentStyle: 'space', indentWidth: 2, lineWidth: 100 },\n },\n null,\n 2,\n ) + '\\n',\n )\n } else if (!options.eslint) {\n // No linter\n const eslintPath = resolve(targetDir, 'eslint.config.mjs')\n if (existsSync(eslintPath)) unlinkSync(eslintPath)\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n delete pkg.scripts.lint\n delete pkg.scripts['lint:fix']\n delete pkg.devDependencies.eslint\n delete pkg.devDependencies['eslint-config-prettier']\n delete pkg.devDependencies['typescript-eslint']\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n }\n\n // Tailwind\n if (options.tailwind) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n pkg.devDependencies = pkg.devDependencies ?? {}\n pkg.devDependencies.tailwindcss = '^4.0.0'\n pkg.devDependencies['@tailwindcss/vite'] = '^4.0.0'\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n const cssDir = existsSync(resolve(targetDir, 'src/app')) ? 'src/app' : 'app'\n writeFileSync(resolve(targetDir, `${cssDir}/globals.css`), '@import \"tailwindcss\";\\n')\n }\n\n // src/ directory\n if (options.srcDir) {\n const srcDir = resolve(targetDir, 'src')\n mkdirSync(srcDir, { recursive: true })\n for (const dir of ['app', 'server']) {\n const from = resolve(targetDir, dir)\n const to = resolve(srcDir, dir)\n if (existsSync(from)) renameSync(from, to)\n }\n const appFrom = resolve(targetDir, 'app.ts')\n if (existsSync(appFrom)) renameSync(appFrom, resolve(srcDir, 'app.ts'))\n\n const tscPath = resolve(targetDir, 'tsconfig.json')\n const tsc = JSON.parse(readFileSync(tscPath, 'utf-8'))\n tsc.compilerOptions.baseUrl = 'src'\n tsc.include = ['src/**/*.ts', 'src/**/*.tsx']\n writeFileSync(tscPath, JSON.stringify(tsc, null, 2) + '\\n')\n\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n pkg.scripts.dev = 'npx tsx --watch src/app.ts'\n pkg.scripts['dev:bun'] = 'bun --watch src/app.ts'\n pkg.scripts.build = 'npx tsup src/app.ts --format esm --out-dir dist'\n writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\\n')\n }\n\n // Import alias (custom or disabled)\n const tscPath = resolve(targetDir, 'tsconfig.json')\n const tsc = JSON.parse(readFileSync(tscPath, 'utf-8'))\n if (!options.aliases) {\n delete tsc.compilerOptions.baseUrl\n delete tsc.compilerOptions.paths\n } else if (opts.importAlias !== '@/*') {\n // Custom alias: replace @/* with user choice\n const prefix = opts.importAlias.replace('/*', '')\n tsc.compilerOptions.paths = {\n [`${prefix}/*`]: ['./*'],\n [`${prefix}/server/*`]: ['./server/*'],\n [`${prefix}/app/*`]: ['./app/*'],\n }\n }\n writeFileSync(tscPath, JSON.stringify(tsc, null, 2) + '\\n')\n\n // AGENTS.md\n if (!options.agentsMd) {\n const agentsPath = resolve(targetDir, 'AGENTS.md')\n if (existsSync(agentsPath)) unlinkSync(agentsPath)\n }\n}\n\n// ── Git init ──\n\nfunction initGit(targetDir: string): void {\n try {\n execSync('git init', { cwd: targetDir, stdio: 'ignore' })\n execSync('git add -A', { cwd: targetDir, stdio: 'ignore' })\n execSync('git commit -m \"Initial commit from create-theokit\" --no-verify', {\n cwd: targetDir,\n stdio: 'ignore',\n })\n } catch {\n // git not installed — silently skip\n }\n}\n\n// ── Example cloning ──\n\nfunction cloneExample(example: string, targetDir: string): void {\n const isUrl = example.startsWith('http://') || example.startsWith('https://')\n const repo = isUrl\n ? example\n : `https://github.com/usetheodev/theokit-examples/tree/main/${example}`\n\n if (isUrl) {\n // Full repo URL — shallow clone\n execSync(`git clone --depth 1 ${repo} ${targetDir}`, { stdio: 'inherit' })\n // Remove .git to let user init fresh\n rmSync(join(targetDir, '.git'), { recursive: true, force: true })\n } else {\n // Named example from theokit-examples repo — use degit pattern\n try {\n execSync(`npx --yes degit usetheodev/theokit-examples/${example} ${targetDir}`, {\n stdio: 'inherit',\n })\n } catch {\n throw new Error(\n `Example \"${example}\" not found. ` +\n `Browse available examples at https://github.com/usetheodev/theokit-examples`,\n )\n }\n }\n}\n\n// Auto-execute\nmain().catch((err: unknown) => {\n console.error(err)\n process.exit(1)\n})\n","import spawn from 'cross-spawn'\n\nimport type { PkgManager } from './pkg-manager.js'\n\nexport function runInstall(cwd: string, pkgManager: PkgManager): void {\n const result = spawn.sync(pkgManager, ['install'], {\n cwd,\n stdio: 'inherit',\n env: { ...process.env, NODE_ENV: 'development' },\n })\n\n if (result.status !== 0) {\n throw new Error(`Failed to install dependencies with ${pkgManager}`)\n }\n}\n","export type PkgManager = 'npm' | 'pnpm' | 'yarn' | 'bun'\n\nexport function detectPkgManager(): PkgManager {\n const ua = process.env.npm_config_user_agent ?? ''\n if (ua.startsWith('yarn')) return 'yarn'\n if (ua.startsWith('pnpm')) return 'pnpm'\n if (ua.startsWith('bun')) return 'bun'\n return 'npm'\n}\n","/**\n * Node version preflight for `create-theokit`.\n *\n * `@theokit/sdk` declares `engines.node: \">=22.12.0\"`. Users on older Node\n * hit cryptic `node:sqlite` / `better-sqlite3` ABI errors mid-chat without\n * any actionable diagnostic. This preflight runs at scaffold start, prints\n * a clear error, and exits before any FS write (ADR D4 of the plan).\n *\n * Zero-dep semver comparator — no `semver` package needed.\n */\n\n/** Minimum Node version required by `@theokit/sdk`. */\nexport const MIN_NODE_VERSION = '22.12.0'\n\n/**\n * Compare two semver strings (major.minor.patch). Strips leading `v` from\n * either side. Pre-release suffixes (e.g., `-rc.1`, `-nightly...`) are\n * parsed as the base patch — acceptable for a preflight (we're not gating\n * on exact pre-release ordering).\n *\n * Returns:\n * <0 when a < b\n * 0 when a == b\n * >0 when a > b\n */\nexport function compareSemver(a: string, b: string): number {\n const parse = (raw: string): [number, number, number] => {\n const stripped = raw.replace(/^v/, '').split('-')[0] ?? '0.0.0'\n const parts = stripped.split('.').map((s) => Number.parseInt(s, 10) || 0)\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0]\n }\n const [aMaj, aMin, aPat] = parse(a)\n const [bMaj, bMin, bPat] = parse(b)\n if (aMaj !== bMaj) return aMaj - bMaj\n if (aMin !== bMin) return aMin - bMin\n return aPat - bPat\n}\n\n/**\n * Throw with an actionable message if `currentRaw` is below `minimum`.\n * Default minimum is `MIN_NODE_VERSION` (22.12.0).\n *\n * Called as the first line of `create-theokit`'s `main` so the preflight\n * fires before any directory is written.\n */\nexport function assertNodeVersion(currentRaw: string, minimum: string = MIN_NODE_VERSION): void {\n const current = currentRaw.replace(/^v/, '')\n if (compareSemver(currentRaw, minimum) < 0) {\n throw new Error(\n `create-theokit requires Node ${minimum} or later (the @theokit/sdk peer engines floor).\\n` +\n ` Detected: Node ${current}\\n` +\n ` Fix: nvm install 22 && nvm use 22\\n` +\n ` (or your version manager equivalent — fnm, volta, asdf, nvs)\\n`,\n )\n }\n}\n","/**\n * Interactive prompts for create-theokit.\n * Skipped when --yes flag is passed (uses defaults).\n *\n * Uses Node.js readline (zero deps) — not inquirer/prompts.\n */\nimport { createInterface } from 'node:readline'\n\nexport interface ProjectOptions {\n projectName: string\n typescript: boolean\n eslint: boolean\n tailwind: boolean\n srcDir: boolean\n aliases: boolean\n agentsMd: boolean\n}\n\nconst DEFAULTS: Omit<ProjectOptions, 'projectName'> = {\n typescript: true,\n eslint: true,\n tailwind: true,\n srcDir: false,\n aliases: true,\n agentsMd: true,\n}\n\nasync function ask(rl: ReturnType<typeof createInterface>, question: string, defaultVal: boolean): Promise<boolean> {\n return new Promise((resolve) => {\n const suffix = defaultVal ? '(Y/n)' : '(y/N)'\n rl.question(` ${question} ${suffix} `, (answer) => {\n const a = answer.trim().toLowerCase()\n if (a === '') resolve(defaultVal)\n else resolve(a === 'y' || a === 'yes')\n })\n })\n}\n\nasync function askChoice(rl: ReturnType<typeof createInterface>, question: string, choices: string[], defaultIdx = 0): Promise<number> {\n return new Promise((resolve) => {\n console.log(` ${question}`)\n for (let i = 0; i < choices.length; i++) {\n const marker = i === defaultIdx ? '❯' : ' '\n console.log(` ${marker} ${choices[i]}`)\n }\n rl.question(' Choice (number): ', (answer) => {\n const n = parseInt(answer.trim(), 10)\n resolve(n >= 1 && n <= choices.length ? n - 1 : defaultIdx)\n })\n })\n}\n\nexport async function runPrompts(projectName: string): Promise<ProjectOptions> {\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n\n try {\n console.log('')\n const mode = await askChoice(rl, 'Would you like to use the recommended TheoKit defaults?', [\n 'Yes, use recommended defaults — TypeScript, ESLint, Tailwind CSS, AGENTS.md',\n 'No, customize settings',\n ], 0)\n\n if (mode === 0) {\n return { projectName, ...DEFAULTS }\n }\n\n const typescript = await ask(rl, 'Would you like to use TypeScript?', true)\n const eslint = await ask(rl, 'Would you like to use ESLint?', true)\n const tailwind = await ask(rl, 'Would you like to use Tailwind CSS?', true)\n const srcDir = await ask(rl, 'Would you like your code inside a `src/` directory?', false)\n const aliases = await ask(rl, 'Would you like to use import alias (@/*)?', true)\n const agentsMd = await ask(rl, 'Would you like to include AGENTS.md for coding agents?', true)\n\n return { projectName, typescript, eslint, tailwind, srcDir, aliases, agentsMd }\n } finally {\n rl.close()\n }\n}\n\nexport function getDefaults(projectName: string): ProjectOptions {\n return { projectName, ...DEFAULTS }\n}\n","/**\n * Build-time scaffold helper. All write paths derived from the trusted\n * targetDir (CLI argument, resolved absolute). Read paths are the\n * bundled service templates shipped with this package.\n */\n/**\n * Phase 4 — `--backend python|node` scaffolding (T4.1, T4.2).\n *\n * After the main TheoKit scaffold runs, this helper:\n * - Copies the requested service template(s) under `<target>/services/<name>/`\n * - Substitutes `{{name}}` in `.tmpl` files\n * - Renames `.tmpl` files to drop the suffix\n * - Injects services config into the user's `theo.config.ts`\n * - Injects `@hey-api/client-fetch` into the user's package.json (EC-10)\n */\nimport {\n cpSync,\n existsSync,\n readFileSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n} from 'node:fs'\nimport { dirname, join, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport type BackendKind = 'python' | 'node'\n\nconst VALID_BACKENDS = ['python', 'node'] as const\n\n/**\n * Parse `--backend python` / `--backend node` (multi-value) from argv.\n *\n * Accepts:\n * --backend python\n * --backend=python\n * --backend python --backend node\n *\n * Throws on unknown backend name.\n */\nexport function parseBackendFlags(args: string[]): BackendKind[] {\n const out: BackendKind[] = []\n for (let i = 0; i < args.length; i++) {\n const a = args[i] ?? ''\n let value: string | undefined\n if (a === '--backend') {\n value = args[i + 1]\n i++\n } else if (a.startsWith('--backend=')) {\n value = a.slice('--backend='.length)\n }\n if (value === undefined) continue\n if (!(VALID_BACKENDS as readonly string[]).includes(value)) {\n throw new Error(\n `unknown --backend value: '${value}'. Valid options: ${VALID_BACKENDS.join(', ')}.`,\n )\n }\n out.push(value as BackendKind)\n }\n return out\n}\n\nconst BACKEND_CONFIG: Record<\n BackendKind,\n {\n templateDir: string\n serviceName: string\n port: number\n proxy: string\n dev: string\n start: string\n }\n> = {\n python: {\n templateDir: 'agent-python',\n serviceName: 'agent',\n port: 8001,\n proxy: '/api/agent',\n dev: 'uvicorn main:app --reload --port 8001',\n start: 'uvicorn main:app --port 8001 --workers 4',\n },\n node: {\n templateDir: 'agent-node',\n serviceName: 'worker',\n port: 8002,\n proxy: '/api/worker',\n dev: 'pnpm dev',\n start: 'pnpm start',\n },\n}\n\nfunction getServiceTemplateDir(kind: BackendKind): string {\n return resolve(__dirname, '../templates/services', BACKEND_CONFIG[kind].templateDir)\n}\n\nfunction substituteTmpls(dir: string, projectName: string): void {\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const stat = statSync(full)\n if (stat.isDirectory()) {\n substituteTmpls(full, projectName)\n continue\n }\n if (entry.endsWith('.tmpl')) {\n const content = readFileSync(full, 'utf-8').replace(/\\{\\{name\\}\\}/g, projectName)\n const dest = full.replace(/\\.tmpl$/, '')\n writeFileSync(dest, content)\n unlinkSync(full)\n }\n }\n}\n\ninterface ServiceEntry {\n runtime: BackendKind\n port: number\n proxy: string\n dev: string\n start: string\n}\n\n/**\n * Build the `services: {}` snippet to inject into `theo.config.ts`.\n * Returns the inner record literal — caller wraps in `services: { ... }`.\n */\nexport function buildServicesSnippet(selections: { name: string; entry: ServiceEntry }[]): string {\n if (selections.length === 0) return ''\n const blocks = selections.map(({ name, entry }) => {\n return ` ${name}: {\n runtime: '${entry.runtime}',\n port: ${String(entry.port)},\n proxy: '${entry.proxy}',\n dev: ${JSON.stringify(entry.dev)},\n start: ${JSON.stringify(entry.start)},\n },`\n })\n return ` services: {\\n${blocks.join('\\n')}\\n },\\n`\n}\n\n/**\n * Insert the services block into an existing `theo.config.ts`.\n * Strategy: find `defineConfig({` and append the services block before the closing brace.\n */\nexport function injectServicesIntoConfig(configSource: string, snippet: string): string {\n if (snippet.length === 0) return configSource\n if (configSource.includes('services:')) return configSource // already present\n\n // Match the LAST `}` before the closing of defineConfig({...})\n const re = /defineConfig\\(\\{([\\s\\S]*?)\\}\\)/m\n const match = re.exec(configSource)\n if (!match) return configSource\n\n const inner = match[1]\n // Strip trailing whitespace without backtracking-prone \\s+$ regex.\n let trimEnd = inner.length\n while (trimEnd > 0 && /\\s/.test(inner.charAt(trimEnd - 1))) {\n trimEnd--\n }\n const trimmed = inner.slice(0, trimEnd)\n const sep = trimmed.length > 0 && !trimmed.endsWith(',') ? ',\\n' : '\\n'\n const newInner = `${trimmed}${sep}${snippet}`\n return configSource.replace(re, `defineConfig({${newInner}})`)\n}\n\nexport function injectHeyApiDep(packageJsonSource: string): string {\n const pkg = JSON.parse(packageJsonSource) as {\n dependencies?: Record<string, string>\n }\n pkg.dependencies = pkg.dependencies ?? {}\n if (!('@hey-api/client-fetch' in pkg.dependencies)) {\n pkg.dependencies['@hey-api/client-fetch'] = '^0.6.0'\n }\n return JSON.stringify(pkg, null, 2) + '\\n'\n}\n\nexport interface ScaffoldServicesOptions {\n /** Target project directory (already scaffolded with the TS template). */\n targetDir: string\n /** Project name (substituted into .tmpl files). */\n projectName: string\n /** Which backends to scaffold. */\n backends: BackendKind[]\n}\n\nexport function scaffoldServices(options: ScaffoldServicesOptions): void {\n if (options.backends.length === 0) return\n\n const selections: { name: string; entry: ServiceEntry }[] = []\n\n for (const kind of options.backends) {\n const cfg = BACKEND_CONFIG[kind]\n const src = getServiceTemplateDir(kind)\n // Schema contract: dev/start commands run from `services/<serviceName>/` cwd\n // (services/schema/schema.ts line 35). The destination MUST equal serviceName\n // so the orchestrator's cwd resolution matches the on-disk directory.\n const dest = join(options.targetDir, 'services', cfg.serviceName)\n if (!existsSync(src)) {\n throw new Error(`service template not found: ${src}`)\n }\n cpSync(src, dest, { recursive: true })\n substituteTmpls(dest, options.projectName)\n\n selections.push({\n name: cfg.serviceName,\n entry: {\n runtime: kind,\n port: cfg.port,\n proxy: cfg.proxy,\n dev: cfg.dev,\n start: cfg.start,\n },\n })\n }\n\n // Inject services into theo.config.ts\n const configPath = join(options.targetDir, 'theo.config.ts')\n if (existsSync(configPath)) {\n const cfgSrc = readFileSync(configPath, 'utf-8')\n const snippet = buildServicesSnippet(selections)\n const updated = injectServicesIntoConfig(cfgSrc, snippet)\n if (updated !== cfgSrc) {\n writeFileSync(configPath, updated)\n }\n }\n\n // EC-10: inject @hey-api/client-fetch into user's package.json\n const pkgPath = join(options.targetDir, 'package.json')\n if (existsSync(pkgPath)) {\n const pkgSrc = readFileSync(pkgPath, 'utf-8')\n const updated = injectHeyApiDep(pkgSrc)\n writeFileSync(pkgPath, updated)\n }\n\n // Rename .gitignore for services if needed (none currently shipped, but reserved hook)\n}\n","/**\n * `create-theokit` scaffold tool. All write paths are derived from the\n * user-supplied target directory (CLI argument, resolved to absolute).\n * Read paths are the bundled `templates/` shipped with this package.\n * Build-time tool — no HTTP input.\n */\nimport {\n existsSync,\n cpSync,\n readFileSync,\n writeFileSync,\n renameSync,\n unlinkSync,\n readdirSync,\n rmSync,\n statSync,\n} from 'node:fs'\nimport { resolve, join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nimport { applyBareTransform } from './bare-transform.js'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nfunction getTemplateDir(templateName = 'default'): string {\n return resolve(__dirname, '../templates', templateName)\n}\n\nfunction isValidProjectName(name: string): boolean {\n return /^[a-z0-9][a-z0-9._-]*$/.test(name)\n}\n\nexport interface ScaffoldOptions {\n bare?: boolean\n /** Test-only — force the bare transform to throw to validate EC-4 rollback. */\n _testForceTransformError?: string\n}\n\nexport function scaffold(\n targetDir: string,\n projectName: string,\n templateName = 'default',\n options: ScaffoldOptions = {},\n): void {\n // EC-4 + ADR D5: --bare only applies to default template\n if (options.bare && templateName !== 'default') {\n throw new Error(\n `--bare flag only applies to the default template; got \"${templateName}\". ` +\n `Use \\`npx create-theokit <name> --template=default --bare\\` or pick a different template.`,\n )\n }\n\n const templateDir = getTemplateDir(templateName)\n\n if (!existsSync(templateDir)) {\n throw new Error(\n `Template \"${templateName}\" not found. Available templates: default, dashboard, api-only, postgres, saas`,\n )\n }\n\n // Bug #1 fix: \".\" means current directory — use dir basename as project name\n const resolvedName =\n projectName === '.' ? (resolve(targetDir).split('/').pop() ?? 'my-app') : projectName\n\n if (!isValidProjectName(resolvedName)) {\n throw new Error(\n `Invalid project name \"${resolvedName}\". ` +\n `Use lowercase letters, numbers, hyphens, and dots. Must start with a letter or number.`,\n )\n }\n\n if (existsSync(targetDir)) {\n const contents = readdirSync(targetDir)\n if (contents.length > 0) {\n throw new Error(`Directory \"${targetDir}\" is not empty. Please use an empty directory.`)\n }\n }\n\n cpSync(templateDir, targetDir, { recursive: true })\n\n const gitignoreSrc = join(targetDir, '_gitignore')\n const gitignoreDest = join(targetDir, '.gitignore')\n if (existsSync(gitignoreSrc)) {\n renameSync(gitignoreSrc, gitignoreDest)\n }\n\n // Apply {{name}} substitution to every `*.tmpl` file in the target dir.\n // Each `foo.tmpl` becomes `foo` with placeholders replaced. Walks only\n // the project root (deeper subfolders don't currently need templating).\n for (const entry of readdirSync(targetDir)) {\n if (!entry.endsWith('.tmpl')) continue\n const src = join(targetDir, entry)\n const stat = statSync(src)\n if (!stat.isFile()) continue\n const dst = join(targetDir, entry.slice(0, -'.tmpl'.length))\n const content = readFileSync(src, 'utf-8')\n const replaced = content.replace(/\\{\\{name\\}\\}/g, resolvedName)\n writeFileSync(dst, replaced)\n unlinkSync(src)\n }\n\n // T4.1 — Apply --bare transform with EC-4 atomic rollback\n if (options.bare) {\n try {\n applyBareTransform(targetDir, {\n _testForceError: options._testForceTransformError,\n })\n } catch (err) {\n // EC-4: roll back partial state\n rmSync(targetDir, { recursive: true, force: true })\n const original = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Scaffold rolled back: bare transform failed. Check filesystem perms.\\nOriginal error: ${original}`,\n )\n }\n }\n}\n","/**\n * Scaffold transform. Mutates files inside the freshly-created target\n * directory whose absolute path is the function input. No HTTP input.\n */\n/**\n * T4.1 — `--bare` transformation.\n *\n * Applied AFTER the default template is copied. Removes:\n * - `@theokit/ui` from `package.json` dependencies (TheoUI bundled components)\n * - `@theokit/sdk` from `package.json` dependencies (agent SDK — see below)\n * - `app/page.tsx` agent-surface content (replaces with Hello Theo)\n * - `server/routes/chat.ts` (mock chat — depends on SDK + TheoUI events)\n * - `tailwind.config.ts` + `postcss.config.js` (Tailwind toolchain — only\n * needed by the @theokit/ui-driven default surface)\n * - tailwind* + postcss* from devDependencies (toolchain cleanup)\n *\n * Why SDK removal is in --bare:\n * `@theokit/sdk` is not yet on the public npm registry (operator-deferred\n * publish per macro roadmap item #3). A user running `npx create-theokit`\n * without `--bare` hits `npm install` → 404. The `--bare` path produces a\n * scaffold that ALWAYS works without registry dependencies — Hello Theo\n * with a clean structure to grow into.\n *\n * EC-4: callers MUST wrap this in try/catch + `rmSync` rollback so a partial\n * transform never leaves the target dir in a broken state.\n */\n\nimport { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\n\nconst HELLO_PAGE = `export default function Page() {\n return <h1>Hello Theo</h1>\n}\n`\n\nexport interface BareTransformOptions {\n /** Test-only — force a synthetic write failure to validate rollback path. */\n _testForceError?: string\n}\n\nexport function applyBareTransform(targetDir: string, options: BareTransformOptions = {}): void {\n if (options._testForceError) {\n throw new Error(`Forced transform failure: ${options._testForceError}`)\n }\n\n // 1. Remove @theokit/ui + @theokit/sdk + tailwind toolchain from deps\n const pkgPath = join(targetDir, 'package.json')\n if (existsSync(pkgPath)) {\n interface PartialPackageJson {\n dependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n [key: string]: unknown\n }\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as PartialPackageJson\n if (pkg.dependencies) {\n delete pkg.dependencies['@theokit/ui']\n // Drop SDK — operator-deferred npm publish (macro roadmap item #3).\n // Without removal, `npm install` hits 404 for any consumer outside\n // the workspace.\n delete pkg.dependencies['@theokit/sdk']\n // lucide-react ships with the TheoUI surface; --bare doesn't render\n // any icons so it's safe to drop.\n delete pkg.dependencies['lucide-react']\n }\n if (pkg.devDependencies) {\n // Tailwind toolchain is only needed by the @theokit/ui-driven default\n // surface. --bare ships unstyled Hello Theo; no Tailwind required.\n delete pkg.devDependencies.tailwindcss\n delete pkg.devDependencies['tailwindcss-animate']\n delete pkg.devDependencies.postcss\n delete pkg.devDependencies.autoprefixer\n }\n writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\\n`)\n }\n\n // 2. Replace app/page.tsx with Hello Theo\n const pagePath = join(targetDir, 'app/page.tsx')\n if (existsSync(pagePath)) {\n writeFileSync(pagePath, HELLO_PAGE)\n }\n\n // 3. Remove mock chat route (depends on AgentEvent type and TheoUI deps)\n const chatPath = join(targetDir, 'server/routes/chat.ts')\n if (existsSync(chatPath)) {\n unlinkSync(chatPath)\n }\n\n // 4. Remove tailwind + postcss config files (toolchain dropped from devDeps)\n const tailwindCfg = join(targetDir, 'tailwind.config.ts')\n if (existsSync(tailwindCfg)) {\n unlinkSync(tailwindCfg)\n }\n const postcssCfg = join(targetDir, 'postcss.config.js')\n if (existsSync(postcssCfg)) {\n unlinkSync(postcssCfg)\n }\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","unlinkSync","renameSync","existsSync","rmSync","resolve","join","resolve","existsSync","cpSync","readFileSync","writeFileSync","unlinkSync","readdirSync","statSync","resolve","join","dirname","fileURLToPath","existsSync","readFileSync","writeFileSync","unlinkSync","join","__dirname","dirname","fileURLToPath","resolve","existsSync","readdirSync","cpSync","join","statSync","readFileSync","writeFileSync","unlinkSync","resolve","existsSync","unlinkSync","readFileSync","writeFileSync","renameSync","tscPath","tsc","rmSync","join"]}
|
package/package.json
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# AGENTS.md — TheoKit App
|
|
2
|
+
|
|
3
|
+
Guide for coding agents (Claude, Copilot, Cursor) working on this TheoKit project.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
This is a **full-stack TypeScript app** built with TheoKit — a framework for AI agent apps.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
app.ts → Entry point: TheoApp.create({ controllers, agents, providers })
|
|
11
|
+
server/
|
|
12
|
+
controllers/ → HTTP endpoints (@Controller + @Get/@Post/@Delete)
|
|
13
|
+
agents/ → AI agents (@Agent + @MainLoop + @Tool)
|
|
14
|
+
toolboxes/ → Agent tools (@Toolbox + @Tool with Zod schemas)
|
|
15
|
+
guards/ → Auth/RBAC (@UseGuards + canActivate)
|
|
16
|
+
interceptors/ → Request/response transforms (@UseInterceptors)
|
|
17
|
+
filters/ → Error formatting (@UseFilters + @Catch)
|
|
18
|
+
middleware/ → Request pipeline (NestMiddleware)
|
|
19
|
+
store.ts → In-memory data store
|
|
20
|
+
app/
|
|
21
|
+
page.tsx → React frontend
|
|
22
|
+
layout.tsx → Root layout
|
|
23
|
+
public/
|
|
24
|
+
index.html → Static HTML frontend with chat UI
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Key Patterns
|
|
28
|
+
|
|
29
|
+
### Controllers (HTTP API)
|
|
30
|
+
```typescript
|
|
31
|
+
import { Controller, Get, Post, Body, Param } from '@theokit/http'
|
|
32
|
+
import { z } from 'zod'
|
|
33
|
+
|
|
34
|
+
const zCreate = z.object({ title: z.string().min(3) })
|
|
35
|
+
|
|
36
|
+
@Controller('api/tasks')
|
|
37
|
+
class TasksController {
|
|
38
|
+
@Get()
|
|
39
|
+
list() { return tasks }
|
|
40
|
+
|
|
41
|
+
@Post()
|
|
42
|
+
create(@Body(zCreate) body: z.infer<typeof zCreate>) {
|
|
43
|
+
return store.create(body) // 201 automatic for POST
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Agents (AI with tools)
|
|
49
|
+
```typescript
|
|
50
|
+
import { Agent, MainLoop, Toolbox, Tool, Mixin } from '@theokit/agents'
|
|
51
|
+
|
|
52
|
+
@Agent({ name: 'assistant', route: '/api/agents/assistant', model: 'openai/gpt-4o-mini' })
|
|
53
|
+
@Mixin(TaskTools)
|
|
54
|
+
class AssistantAgent {
|
|
55
|
+
@MainLoop({ strategy: 'react' })
|
|
56
|
+
async run() {}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Validation
|
|
61
|
+
- **Zod is the single source of truth** — define schema once, get types + validation + OpenAPI
|
|
62
|
+
- `@Body(zodSchema)` validates automatically, returns 422 on failure
|
|
63
|
+
- Use `z.infer<typeof schema>` for TypeScript types
|
|
64
|
+
|
|
65
|
+
### Error Handling
|
|
66
|
+
- `throw new NotFoundException('...')` → 404
|
|
67
|
+
- `throw new BadRequestException('...')` → 400
|
|
68
|
+
- `@UseFilters(MyFilter)` for custom error format
|
|
69
|
+
- 500 errors are scrubbed — raw messages never reach clients
|
|
70
|
+
|
|
71
|
+
### Guards (Auth/RBAC)
|
|
72
|
+
- `@UseGuards(AuthGuard)` on controller or agent
|
|
73
|
+
- Guards apply to BOTH HTTP and agent routes (shared pipeline)
|
|
74
|
+
- `canActivate(ctx: ExecutionContext): boolean`
|
|
75
|
+
|
|
76
|
+
### Path Aliases
|
|
77
|
+
- `@/*` → project root (configured in tsconfig.json)
|
|
78
|
+
- `@/server/*` → `./server/*`
|
|
79
|
+
|
|
80
|
+
## Commands
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm run dev # Start dev server (tsx --watch)
|
|
84
|
+
npm run build # Build for production (tsup)
|
|
85
|
+
npm run start # Run production build
|
|
86
|
+
npm run lint # ESLint check
|
|
87
|
+
npm run format # Prettier format
|
|
88
|
+
npm run typecheck # TypeScript type check
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Don't
|
|
92
|
+
|
|
93
|
+
- Don't use `any` — use Zod schemas + `z.infer<>`
|
|
94
|
+
- Don't write raw `res.status().json()` — use decorators (`@HttpCode`, `@Header`)
|
|
95
|
+
- Don't create separate auth middleware for agents — use `@UseGuards` (same as controllers)
|
|
96
|
+
- Don't parse request body manually — use `@Body(zodSchema)`
|
package/templates/default/app.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TheoKit App —
|
|
2
|
+
* TheoKit App — convention over configuration.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - DI (providers injected into controllers + agents)
|
|
9
|
-
* - Shared pipeline (guards, interceptors, filters)
|
|
4
|
+
* "The framework that reduces noise for humans + AI."
|
|
5
|
+
*
|
|
6
|
+
* Backend classes registered in server/index.ts (one barrel, like Rails).
|
|
7
|
+
* Routes inferred from class names. Zero manual wiring.
|
|
10
8
|
*/
|
|
11
9
|
import 'reflect-metadata'
|
|
12
10
|
import { readFileSync } from 'node:fs'
|
|
13
|
-
import { TheoApp } from '@theokit/http
|
|
14
|
-
import { TasksController } from './server/
|
|
15
|
-
import { AssistantAgent } from './server/agents/assistant.agent.js'
|
|
16
|
-
import { TaskTools } from './server/toolboxes/task.tools.js'
|
|
11
|
+
import { TheoApp } from '@theokit/http/app'
|
|
12
|
+
import { TasksController, AssistantAgent, TaskTools } from './server/index.js'
|
|
17
13
|
|
|
18
|
-
// Frontend HTML (inline for alpha — upgrade to Vite plugin for React SSR)
|
|
19
14
|
let html: string | undefined
|
|
20
15
|
try { html = readFileSync(new URL('./public/index.html', import.meta.url), 'utf-8') } catch { /* no frontend */ }
|
|
21
16
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint'
|
|
2
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
3
|
+
|
|
4
|
+
export default tseslint.config(
|
|
5
|
+
{ ignores: ['dist/', 'node_modules/'] },
|
|
6
|
+
...tseslint.configs.recommended,
|
|
7
|
+
prettierConfig,
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
11
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
)
|
|
@@ -4,18 +4,30 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "
|
|
8
|
-
"dev:
|
|
9
|
-
"
|
|
7
|
+
"dev": "npx tsx --watch app.ts",
|
|
8
|
+
"dev:bun": "bun --watch app.ts",
|
|
9
|
+
"build": "npx tsup app.ts --format esm --out-dir dist",
|
|
10
|
+
"start": "node dist/app.js",
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"lint:fix": "eslint . --fix",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"format:check": "prettier --check .",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
10
16
|
},
|
|
11
17
|
"dependencies": {
|
|
12
|
-
"@theokit/http
|
|
13
|
-
"@theokit/agents": "^0.
|
|
18
|
+
"@theokit/http": "^0.4.0",
|
|
19
|
+
"@theokit/agents": "^0.3.0",
|
|
14
20
|
"reflect-metadata": "^0.2.0",
|
|
15
21
|
"zod": "^4.0.0"
|
|
16
22
|
},
|
|
17
23
|
"devDependencies": {
|
|
24
|
+
"@swc/core": "^1.3.0",
|
|
25
|
+
"eslint": "^9.0.0",
|
|
26
|
+
"eslint-config-prettier": "^10.0.0",
|
|
27
|
+
"prettier": "^3.0.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"tsx": "^4.19.0",
|
|
18
30
|
"typescript": "^5.5.0",
|
|
19
|
-
"
|
|
31
|
+
"typescript-eslint": "^8.0.0"
|
|
20
32
|
}
|
|
21
33
|
}
|
|
@@ -9,20 +9,17 @@ import {
|
|
|
9
9
|
Agent, MainLoop, Mixin,
|
|
10
10
|
Memory, Budget, Hook,
|
|
11
11
|
} from '@theokit/agents'
|
|
12
|
-
import { UseGuards, UseInterceptors } from '@theokit/http
|
|
12
|
+
import { UseGuards, UseInterceptors } from '@theokit/http'
|
|
13
13
|
import { RolesGuard, Roles, Role } from '../guards/auth.guard.js'
|
|
14
14
|
import { TimingInterceptor } from '../interceptors/timing.interceptor.js'
|
|
15
15
|
import { TaskTools } from '../toolboxes/task.tools.js'
|
|
16
16
|
|
|
17
17
|
@Agent({
|
|
18
|
-
name: 'assistant',
|
|
19
|
-
route: '/api/agents/assistant',
|
|
18
|
+
// Convention: AssistantAgent → name: 'assistant', route: /api/agents/assistant
|
|
20
19
|
model: 'openai/gpt-4o-mini',
|
|
21
20
|
systemPrompt: `You are a helpful task management assistant.
|
|
22
21
|
Use the tasks.* tools to list, search, create, and complete tasks.
|
|
23
22
|
Be concise and actionable.`,
|
|
24
|
-
stream: true,
|
|
25
|
-
maxIterations: 5,
|
|
26
23
|
})
|
|
27
24
|
@UseGuards(RolesGuard)
|
|
28
25
|
@UseInterceptors(TimingInterceptor)
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Body, Param, Query, HttpCode,
|
|
13
13
|
UseGuards, UseInterceptors, UseFilters,
|
|
14
14
|
NotFoundException,
|
|
15
|
-
} from '@theokit/http
|
|
15
|
+
} from '@theokit/http'
|
|
16
16
|
import { RolesGuard, Roles, Role, IsPublic } from '../guards/auth.guard.js'
|
|
17
17
|
import { TimingInterceptor } from '../interceptors/timing.interceptor.js'
|
|
18
18
|
import { HttpErrorFilter } from '../filters/http-error.filter.js'
|
|
@@ -23,7 +23,7 @@ const zCreateTask = z.object({
|
|
|
23
23
|
priority: z.enum(['low', 'medium', 'high']).default('medium'),
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
@Controller(
|
|
26
|
+
@Controller() // → /api/tasks (convention: inferred from TasksController)
|
|
27
27
|
@UseGuards(RolesGuard)
|
|
28
28
|
@UseInterceptors(TimingInterceptor)
|
|
29
29
|
@UseFilters(HttpErrorFilter)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* HttpErrorFilter — custom error response format.
|
|
3
3
|
* Catches HttpException and returns structured JSON.
|
|
4
4
|
*/
|
|
5
|
-
import { Catch, HttpException, type ExceptionFilter, type ArgumentsHost } from '@theokit/http
|
|
5
|
+
import { Catch, HttpException, type ExceptionFilter, type ArgumentsHost } from '@theokit/http'
|
|
6
6
|
|
|
7
7
|
@Catch(HttpException)
|
|
8
8
|
export class HttpErrorFilter implements ExceptionFilter {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server module — auto-exports all controllers, agents, and providers.
|
|
3
|
+
*
|
|
4
|
+
* Convention: add your classes here. TheoApp.create() imports this one file.
|
|
5
|
+
* Like Rails' application.rb — single entry point for the backend.
|
|
6
|
+
*
|
|
7
|
+
* When you run `theokit generate controller/agent/toolbox`, the generated
|
|
8
|
+
* file is auto-added here.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Controllers — HTTP API
|
|
12
|
+
export { TasksController } from './controllers/tasks.controller.js'
|
|
13
|
+
|
|
14
|
+
// Agents — AI endpoints
|
|
15
|
+
export { AssistantAgent } from './agents/assistant.agent.js'
|
|
16
|
+
|
|
17
|
+
// Providers — DI (toolboxes, services)
|
|
18
|
+
export { TaskTools } from './toolboxes/task.tools.js'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* TimingInterceptor — logs request duration.
|
|
3
3
|
* Applied to both controllers and agents.
|
|
4
4
|
*/
|
|
5
|
-
import type { Interceptor } from '@theokit/http
|
|
5
|
+
import type { Interceptor } from '@theokit/http'
|
|
6
6
|
|
|
7
7
|
export class TimingInterceptor implements Interceptor {
|
|
8
8
|
async intercept(_request: Request, next: () => Promise<unknown>): Promise<unknown> {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* LoggerMiddleware — logs every incoming request.
|
|
3
3
|
*/
|
|
4
|
-
import type { NestMiddleware } from '@theokit/http
|
|
4
|
+
import type { NestMiddleware } from '@theokit/http'
|
|
5
5
|
|
|
6
6
|
export class LoggerMiddleware implements NestMiddleware {
|
|
7
7
|
use(request: Request, next: () => Promise<Response | null>): Promise<Response | null> {
|
|
@@ -10,7 +10,7 @@ import { z } from 'zod'
|
|
|
10
10
|
import { Toolbox, Tool, Trace, Audit } from '@theokit/agents'
|
|
11
11
|
import { taskStore } from '../store.js'
|
|
12
12
|
|
|
13
|
-
@Toolbox(
|
|
13
|
+
@Toolbox() // Convention: TaskTools → namespace: 'task'
|
|
14
14
|
@Trace(true)
|
|
15
15
|
export class TaskTools {
|
|
16
16
|
@Tool({
|
|
@@ -8,7 +8,13 @@
|
|
|
8
8
|
"strict": true,
|
|
9
9
|
"esModuleInterop": true,
|
|
10
10
|
"skipLibCheck": true,
|
|
11
|
-
"outDir": "dist"
|
|
11
|
+
"outDir": "dist",
|
|
12
|
+
"baseUrl": ".",
|
|
13
|
+
"paths": {
|
|
14
|
+
"@/*": ["./*"],
|
|
15
|
+
"@/server/*": ["./server/*"],
|
|
16
|
+
"@/app/*": ["./app/*"]
|
|
17
|
+
}
|
|
12
18
|
},
|
|
13
19
|
"include": ["**/*.ts", "**/*.tsx"],
|
|
14
20
|
"exclude": ["node_modules", "dist"]
|