create-theokit 0.4.0-beta.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/cli.js +4 -3
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +8 -4
  4. package/templates/default/.env.example +5 -0
  5. package/templates/default/README.md.tmpl +71 -57
  6. package/templates/default/_gitignore +2 -3
  7. package/templates/default/app/layout.tsx +51 -82
  8. package/templates/default/app/page.tsx +169 -271
  9. package/templates/default/app.ts +29 -0
  10. package/templates/default/package.json.tmpl +9 -23
  11. package/templates/default/public/index.html +70 -0
  12. package/templates/default/server/agents/assistant.agent.ts +46 -0
  13. package/templates/default/server/controllers/tasks.controller.ts +70 -0
  14. package/templates/default/server/filters/http-error.filter.ts +20 -0
  15. package/templates/default/server/guards/auth.guard.ts +47 -0
  16. package/templates/default/server/interceptors/timing.interceptor.ts +14 -0
  17. package/templates/default/server/middleware/logger.middleware.ts +12 -0
  18. package/templates/default/server/store.ts +46 -0
  19. package/templates/default/server/toolboxes/task.tools.ts +58 -0
  20. package/templates/default/tsconfig.json +7 -7
  21. package/LICENSE +0 -201
  22. package/templates/api-only/.nvmrc +0 -1
  23. package/templates/api-only/README.md.tmpl +0 -78
  24. package/templates/api-only/_gitignore +0 -5
  25. package/templates/api-only/app/page.tsx +0 -3
  26. package/templates/api-only/index.html +0 -12
  27. package/templates/api-only/package.json.tmpl +0 -28
  28. package/templates/api-only/public/.gitkeep +0 -0
  29. package/templates/api-only/public/favicon.ico +0 -0
  30. package/templates/api-only/server/routes/health.ts +0 -5
  31. package/templates/api-only/server/routes/users.ts +0 -27
  32. package/templates/api-only/server/routes/webhooks/echo.ts +0 -34
  33. package/templates/api-only/theo.config.ts +0 -3
  34. package/templates/api-only/tsconfig.json +0 -15
  35. package/templates/dashboard/.nvmrc +0 -1
  36. package/templates/dashboard/README.md.tmpl +0 -76
  37. package/templates/dashboard/_gitignore +0 -5
  38. package/templates/dashboard/app/about/page.tsx +0 -3
  39. package/templates/dashboard/app/dashboard/layout.tsx +0 -10
  40. package/templates/dashboard/app/dashboard/page.tsx +0 -3
  41. package/templates/dashboard/app/layout.tsx +0 -14
  42. package/templates/dashboard/app/page.tsx +0 -8
  43. package/templates/dashboard/index.html +0 -12
  44. package/templates/dashboard/package.json.tmpl +0 -28
  45. package/templates/dashboard/public/.gitkeep +0 -0
  46. package/templates/dashboard/public/favicon.ico +0 -0
  47. package/templates/dashboard/server/crons/cleanup-conversations.ts +0 -59
  48. package/templates/dashboard/server/routes/health.ts +0 -5
  49. package/templates/dashboard/theo.config.ts +0 -3
  50. package/templates/dashboard/tsconfig.json +0 -15
  51. package/templates/default/.nvmrc +0 -1
  52. package/templates/default/index.html +0 -12
  53. package/templates/default/public/.gitkeep +0 -0
  54. package/templates/default/public/favicon.ico +0 -0
  55. package/templates/default/server/crons/cleanup-conversations.ts +0 -59
  56. package/templates/default/server/routes/chat.ts +0 -69
  57. package/templates/default/server/routes/health.ts +0 -5
  58. package/templates/default/theo.config.ts +0 -3
  59. package/templates/default/types/jobs.d.ts +0 -25
  60. package/templates/postgres/.env.example +0 -5
  61. package/templates/postgres/.nvmrc +0 -1
  62. package/templates/postgres/README.md.tmpl +0 -83
  63. package/templates/postgres/_gitignore +0 -5
  64. package/templates/postgres/app/layout.tsx +0 -14
  65. package/templates/postgres/app/page.tsx +0 -8
  66. package/templates/postgres/db/index.ts +0 -7
  67. package/templates/postgres/db/schema.ts +0 -8
  68. package/templates/postgres/drizzle.config.ts +0 -10
  69. package/templates/postgres/index.html +0 -12
  70. package/templates/postgres/package.json.tmpl +0 -36
  71. package/templates/postgres/public/.gitkeep +0 -0
  72. package/templates/postgres/public/favicon.ico +0 -0
  73. package/templates/postgres/server/context.ts +0 -5
  74. package/templates/postgres/server/jobs/log-message.ts +0 -26
  75. package/templates/postgres/server/routes/health.ts +0 -5
  76. package/templates/postgres/server/routes/users.ts +0 -22
  77. package/templates/postgres/theo.config.ts +0 -3
  78. package/templates/postgres/tsconfig.json +0 -15
  79. package/templates/saas/.env.example +0 -7
  80. package/templates/saas/.nvmrc +0 -1
  81. package/templates/saas/README.md.tmpl +0 -103
  82. package/templates/saas/_gitignore +0 -5
  83. package/templates/saas/app/layout.tsx +0 -5
  84. package/templates/saas/app/page.tsx +0 -104
  85. package/templates/saas/db/index.ts +0 -6
  86. package/templates/saas/db/schema.ts +0 -20
  87. package/templates/saas/drizzle.config.ts +0 -10
  88. package/templates/saas/index.html +0 -12
  89. package/templates/saas/package.json.tmpl +0 -38
  90. package/templates/saas/public/.gitkeep +0 -0
  91. package/templates/saas/public/favicon.ico +0 -0
  92. package/templates/saas/server/context.ts +0 -37
  93. package/templates/saas/server/routes/agent.ts +0 -49
  94. package/templates/saas/server/routes/billing/stripe-webhook.ts +0 -49
  95. package/templates/saas/server/routes/login.ts +0 -25
  96. package/templates/saas/server/routes/logout.ts +0 -10
  97. package/templates/saas/server/routes/me.ts +0 -10
  98. package/templates/saas/theo.config.ts +0 -5
  99. package/templates/saas/tsconfig.json +0 -15
  100. package/templates/services/agent-node/Dockerfile.tmpl +0 -20
  101. package/templates/services/agent-node/README.md +0 -38
  102. package/templates/services/agent-node/package.json.tmpl +0 -18
  103. package/templates/services/agent-node/src/index.ts +0 -58
  104. package/templates/services/agent-node/tsconfig.json +0 -13
  105. package/templates/services/agent-python/Dockerfile.tmpl +0 -20
  106. package/templates/services/agent-python/README.md +0 -37
  107. package/templates/services/agent-python/main.py +0 -77
  108. package/templates/services/agent-python/pyproject.toml.tmpl +0 -16
package/dist/cli.js CHANGED
@@ -285,9 +285,10 @@ function scaffold(targetDir, projectName, templateName = "default", options = {}
285
285
  `Template "${templateName}" not found. Available templates: default, dashboard, api-only, postgres, saas`
286
286
  );
287
287
  }
288
- if (!isValidProjectName(projectName)) {
288
+ const resolvedName = projectName === "." ? resolve2(targetDir).split("/").pop() ?? "my-app" : projectName;
289
+ if (!isValidProjectName(resolvedName)) {
289
290
  throw new Error(
290
- `Invalid project name "${projectName}". Use lowercase letters, numbers, hyphens, and dots. Must start with a letter or number.`
291
+ `Invalid project name "${resolvedName}". Use lowercase letters, numbers, hyphens, and dots. Must start with a letter or number.`
291
292
  );
292
293
  }
293
294
  if (existsSync3(targetDir)) {
@@ -309,7 +310,7 @@ function scaffold(targetDir, projectName, templateName = "default", options = {}
309
310
  if (!stat.isFile()) continue;
310
311
  const dst = join3(targetDir, entry.slice(0, -".tmpl".length));
311
312
  const content = readFileSync3(src, "utf-8");
312
- const replaced = content.replace(/\{\{name\}\}/g, projectName);
313
+ const replaced = content.replace(/\{\{name\}\}/g, resolvedName);
313
314
  writeFileSync3(dst, replaced);
314
315
  unlinkSync3(src);
315
316
  }
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 if (!isValidProjectName(projectName)) {\n throw new Error(\n `Invalid project name \"${projectName}\". ` +\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, projectName)\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":"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","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/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":"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","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"]}
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "create-theokit",
3
- "version": "0.4.0-beta.0",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "description": "Scaffold a new TheoKit project",
6
6
  "license": "Apache-2.0",
7
+ "scripts": {
8
+ "build": "tsup"
9
+ },
7
10
  "bin": {
8
11
  "create-theokit": "./dist/cli.js"
9
12
  },
@@ -14,7 +17,8 @@
14
17
  "dependencies": {
15
18
  "cross-spawn": "^7.0.6"
16
19
  },
17
- "scripts": {
18
- "build": "tsup"
20
+ "publishConfig": {
21
+ "provenance": true,
22
+ "access": "public"
19
23
  }
20
- }
24
+ }
@@ -0,0 +1,5 @@
1
+ # LLM API key (OpenRouter, Anthropic, or OpenAI)
2
+ OPENROUTER_API_KEY=sk-or-v1-your-key-here
3
+
4
+ # Optional: override the model declared in @Agent({ model })
5
+ # LLM_MODEL=openai/gpt-4o-mini
@@ -1,75 +1,89 @@
1
1
  # {{name}}
2
2
 
3
- TheoKit project. Build the app your agent lives in routing, auth, real-time, deploy — wired.
3
+ Built with [TheoKit](https://theokit.dev)Full-Stack TypeScript for AI Agents.
4
4
 
5
- > 📚 **Full docs:** https://docs.theokit.dev
6
-
7
- ## Quick start
5
+ ## Getting Started
8
6
 
9
7
  ```bash
10
- # 1. Set your provider key (OpenRouter recommended — one key, any model)
11
- cp .env.example .env # if .env.example shipped; otherwise:
12
- echo 'OPENROUTER_API_KEY=sk-or-v1-...' > .env
13
-
14
- # 2. Boot the dev server
15
- npx theokit dev
16
- ```
17
-
18
- Open the printed URL. The default surface is a chat composer wired to `server/routes/chat.ts`. Drop in your real key in `.env` and the agent responds.
19
-
20
- ## Templates
21
-
22
- - **default** (this one) — TheoUI chat composer + agent route.
23
- - **`--bare`** — Hello Theo with no `@theokit/*` deps. Use this when you want a clean structure to grow into or when the SDK isn't on your registry yet.
24
- - **dashboard** — nested layouts + sidebar.
25
- - **api-only** — server routes without React.
26
- - **postgres** — Drizzle ORM + migrations.
27
- - **saas** — full app with auth, billing, sessions.
8
+ # Install dependencies
9
+ npm install
28
10
 
29
- ## What the framework auto-loads
11
+ # Set your LLM API key
12
+ cp .env.example .env
13
+ # Edit .env with your OpenRouter/Anthropic/OpenAI key
30
14
 
31
- - **`.env` `process.env`**. Edit `.env`; restart the dev server. Server routes read `process.env.MY_KEY` directly.
32
- - **`.theo/` build output cleanup** on every `theokit build`.
33
- - **`.theokit/agents/` LRU GC** on every `theokit dev` startup (default cap: 100 agents).
34
- - **Tailwind + `@theokit/ui` styling** auto-configured when `@theokit/ui` is in your `package.json` AND no consumer `tailwind.config.*` is present. This project ships an explicit `tailwind.config.ts` because the TheoUI v3 surface still requires it. The zero-config path lands when `@theokit/ui` ships its Vite plugin (cross-repo work, pending sign-off).
35
-
36
- For the full story of what's automatic vs deliberate, see [docs.usetheo.dev/zero-config](https://docs.usetheo.dev/zero-config) (or your local `node_modules/theokit/docs/concepts/zero-config.md`).
15
+ # Start development server
16
+ bun dev
17
+ # or: npm run dev:node
18
+ ```
37
19
 
38
- ## Project structure
20
+ ## Project Structure
39
21
 
40
22
  ```
41
- app/ Frontend (file-based routing)
42
- ├── page.tsx / — chat composer
43
- ├── layout.tsx wrapper — TheoUI provider + theme
44
- server/ Backend (explicit routes & actions)
45
- ├── routes/
46
- │ ├── chat.ts POST /api/chat — agent SSE stream
47
- │ └── health.ts GET /api/health
48
- theo.config.ts Framework config
49
- tailwind.config.ts Tailwind theme tokens (TheoUI)
50
- postcss.config.js PostCSS pipeline (Tailwind + autoprefixer)
51
- .env Secrets never committed (.gitignore)
23
+ {{name}}/
24
+ ├── app.ts # Entry point (TheoApp.create)
25
+ ├── server/
26
+ │ ├── controllers/
27
+ │ │ └── tasks.controller.ts # HTTP CRUD (@Controller)
28
+ │ ├── agents/
29
+ └── assistant.agent.ts # AI Agent (@Agent + @MainLoop)
30
+ │ ├── toolboxes/
31
+ │ │ └── task.tools.ts # Agent tools (@Toolbox + @Tool)
32
+ │ ├── guards/
33
+ │ │ └── auth.guard.ts # RBAC (@Roles + RolesGuard)
34
+ │ ├── interceptors/
35
+ │ │ └── timing.interceptor.ts # Request timing
36
+ │ ├── filters/
37
+ │ │ └── http-error.filter.ts # Error formatting
38
+ │ ├── middleware/
39
+ │ │ └── logger.middleware.ts # Request logging
40
+ │ └── store.ts # In-memory data store
41
+ └── .env.example # Environment variables
52
42
  ```
53
43
 
54
- ## Common commands
44
+ ## API Endpoints
45
+
46
+ ### HTTP Controllers
55
47
 
56
- | Command | What it does |
57
- |---|---|
58
- | `npx theokit dev` | Dev server with HMR, devtools overlay, structured logs |
59
- | `npx theokit build` | Production build `.theo/` |
60
- | `npx theokit start` | Serve the production build |
61
- | `npx theokit check` | Lint for upgrade-readiness (CSRF, CSP, zero-config migration hints) |
62
- | `npx theokit generate route foo` | Scaffold a new server route |
63
- | `npx theokit routes` | List all routes + actions detected |
48
+ | Method | Path | Auth | Description |
49
+ |--------|------|------|-------------|
50
+ | GET | /api/tasks | Public | List all tasks |
51
+ | GET | /api/tasks/search?q= | Public | Search tasks |
52
+ | GET | /api/tasks/:id | Public | Get task by ID |
53
+ | POST | /api/tasks | User | Create task |
54
+ | PUT | /api/tasks/:id | User | Update task |
55
+ | DELETE | /api/tasks/:id | Admin | Delete task |
64
56
 
65
- ## What if `npm install` fails on `@theokit/sdk`?
57
+ ### AI Agent
66
58
 
67
- The agent SDK is in soft-release while the npm publish is operator-deferred. If you hit `404 Not Found` for `@theokit/sdk`:
59
+ | Method | Path | Auth | Description |
60
+ |--------|------|------|-------------|
61
+ | POST | /api/agents/assistant/chat | User | Chat with AI (SSE stream) |
68
62
 
69
- - **Quick path**: re-scaffold with `--bare` — Hello Theo without any `@theokit/*` deps.
70
- - **Inside the TheoKit monorepo**: replace `"@theokit/sdk": "^1.0.0"` with `"workspace:*"` in `package.json`.
71
- - **Wait**: track the publish at https://github.com/usetheo/theokit-sdk.
63
+ ## Examples
64
+
65
+ ```bash
66
+ # List tasks
67
+ curl http://localhost:3000/api/tasks
68
+
69
+ # Create a task (requires user role)
70
+ curl -X POST http://localhost:3000/api/tasks \
71
+ -H "Content-Type: application/json" \
72
+ -H "x-role: user" \
73
+ -d '{"title":"New task","priority":"high"}'
74
+
75
+ # Chat with AI agent (SSE streaming)
76
+ curl -N -X POST http://localhost:3000/api/agents/assistant/chat \
77
+ -H "Content-Type: application/json" \
78
+ -H "x-role: user" \
79
+ -d '{"message":"List all tasks and mark the first one as done"}'
80
+ ```
72
81
 
73
- ## License
82
+ ## Key Concepts
74
83
 
75
- Apply your own. The TheoKit framework is Apache-2.0.
84
+ - **@Controller** HTTP routes with Zod validation
85
+ - **@Agent** — AI agent with LLM + tool calling + SSE streaming
86
+ - **@Toolbox** + **@Tool** — Functions the AI can call
87
+ - **@UseGuards** — Same auth for controllers AND agents
88
+ - **@Mixin** — Compose toolboxes into agents
89
+ - **TheoApp.create()** — Zero-wiring bootstrap (like NestFactory.create)
@@ -1,5 +1,4 @@
1
- node_modules
2
- .theo
3
- dist
1
+ node_modules/
2
+ dist/
4
3
  .env
5
4
  .env.local
@@ -1,85 +1,54 @@
1
- import '@theokit/ui/styles.css'
2
-
3
- import { Outlet } from 'react-router'
4
- import {
5
- Sidebar,
6
- TopNav,
7
- AgentProfile,
8
- ThemeSwitcher,
9
- ThemeScript,
10
- Tooltip,
11
- Badge,
12
- CostMeter,
13
- type AgentProfileDescriptor,
14
- } from '@theokit/ui'
15
- import { Bot, MessageSquare, History, Settings } from 'lucide-react'
16
-
17
- const AGENTS: AgentProfileDescriptor[] = [
18
- {
19
- id: 'theo',
20
- name: 'Theo',
21
- description: 'Mock LLM — replace at server/routes/chat.ts.',
22
- tone: 'primary',
23
- initials: 'TH',
24
- badge: 'Online',
25
- },
26
- ]
27
-
28
- export default function RootLayout() {
1
+ export default function Layout({ children }: { children: React.ReactNode }) {
29
2
  return (
30
- <>
31
- <ThemeScript />
32
- <div className="grid h-screen w-screen grid-rows-[auto_1fr] bg-background text-foreground">
33
- <TopNav className="border-border/60 border-b px-4 py-2">
34
- <TopNav.Left>
35
- <Tooltip label="TheoKit agent surface" side="bottom">
36
- <span className="inline-flex items-center gap-2">
37
- <Bot className="size-5 text-primary" aria-hidden />
38
- <span className="font-semibold text-sm tracking-tight">Theo Agent</span>
39
- <Badge variant="outline" size="sm">
40
- v0.1
41
- </Badge>
42
- </span>
43
- </Tooltip>
44
- </TopNav.Left>
45
- <TopNav.Right>
46
- <Tooltip label="Toggle theme" side="bottom" align="end">
47
- <span>
48
- <ThemeSwitcher />
49
- </span>
50
- </Tooltip>
51
- </TopNav.Right>
52
- </TopNav>
53
-
54
- <div className="grid h-full grid-cols-[260px_1fr] overflow-hidden">
55
- <Sidebar className="flex flex-col border-border/60 border-r p-3">
56
- <Sidebar.Header>
57
- <AgentProfile agents={AGENTS} activeId="theo" />
58
- </Sidebar.Header>
59
-
60
- <Sidebar.Section title="Workspace">
61
- <Sidebar.Item icon={MessageSquare} active>
62
- New conversation
63
- </Sidebar.Item>
64
- <Sidebar.Item icon={History}>History</Sidebar.Item>
65
- <Sidebar.Item icon={Settings}>Settings</Sidebar.Item>
66
- </Sidebar.Section>
67
-
68
- <Sidebar.Footer className="mt-auto">
69
- <CostMeter
70
- compact
71
- title="This session"
72
- cost={0.0023}
73
- delta={{ value: 0.0023, period: 'now' }}
74
- />
75
- </Sidebar.Footer>
76
- </Sidebar>
77
-
78
- <main className="flex h-full flex-col overflow-hidden">
79
- <Outlet />
80
- </main>
81
- </div>
82
- </div>
83
- </>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charSet="utf-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
7
+ <title>TheoKit App</title>
8
+ <style dangerouslySetInnerHTML={{ __html: STYLES }} />
9
+ </head>
10
+ <body>{children}</body>
11
+ </html>
84
12
  )
85
13
  }
14
+
15
+ const STYLES = `
16
+ :root { --bg: #0a0a0a; --card: #141414; --border: #2a2a2a; --text: #e0e0e0; --muted: #888; --accent: #6366f1; --green: #22c55e; --red: #ef4444; --yellow: #eab308; }
17
+ * { margin: 0; padding: 0; box-sizing: border-box; }
18
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
19
+ #app { max-width: 1200px; margin: 0 auto; padding: 20px; }
20
+ header { margin-bottom: 24px; }
21
+ h1 { font-size: 1.6rem; } .accent { color: var(--accent); }
22
+ .subtitle { color: var(--muted); font-size: 0.85rem; margin-top: 2px; }
23
+ .role-bar { margin-top: 12px; }
24
+ .role-bar select { padding: 6px 12px; background: #1a1a1a; border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 0.8rem; }
25
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } @media(max-width:768px){.grid{grid-template-columns:1fr;}}
26
+ .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; }
27
+ h2 { font-size: 1rem; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
28
+ .badge { font-size: 0.65rem; padding: 2px 8px; border-radius: 99px; background: #6366f122; color: var(--accent); }
29
+ .badge-ai { background: #eab30822; color: var(--yellow); }
30
+ table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
31
+ th { text-align: left; padding: 8px 4px; color: var(--muted); border-bottom: 1px solid var(--border); font-weight: 500; }
32
+ td { padding: 8px 4px; border-bottom: 1px solid var(--border); }
33
+ tr.done td { opacity: 0.5; text-decoration: line-through; }
34
+ .prio { font-size: 0.7rem; padding: 2px 8px; border-radius: 99px; }
35
+ .prio-high { background: #ef444422; color: var(--red); } .prio-med { background: #eab30822; color: var(--yellow); } .prio-low { background: #22c55e22; color: var(--green); }
36
+ .create-bar { display: flex; gap: 8px; margin-top: 14px; }
37
+ .create-bar input { flex: 1; padding: 8px 12px; background: #1a1a1a; border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 0.85rem; }
38
+ .create-bar select { padding: 8px; background: #1a1a1a; border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 0.8rem; }
39
+ .create-bar button { padding: 8px 16px; background: var(--accent); color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 600; }
40
+ .error { color: var(--red); font-size: 0.8rem; margin-top: 4px; }
41
+ .chat-box { height: 400px; overflow-y: auto; padding: 12px; background: #0d0d0d; border-radius: 8px; margin-bottom: 10px; font-size: 0.85rem; line-height: 1.6; }
42
+ .msg { margin-bottom: 10px; padding: 8px 12px; border-radius: 8px; }
43
+ .msg.user { background: #6366f118; color: var(--accent); }
44
+ .msg.agent { background: #1a1a1a; }
45
+ .msg.tool { background: #eab30810; color: var(--yellow); font-size: 0.78rem; font-family: monospace; }
46
+ .msg.system { color: var(--muted); font-size: 0.78rem; font-style: italic; }
47
+ .msg.error { color: var(--red); font-size: 0.8rem; }
48
+ .chat-bar { display: flex; gap: 8px; }
49
+ .chat-bar input { flex: 1; padding: 10px 14px; background: #1a1a1a; border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.9rem; outline: none; }
50
+ .chat-bar input:focus { border-color: var(--accent); }
51
+ .chat-bar button { padding: 10px 20px; background: var(--accent); color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; }
52
+ .chat-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
53
+ .cost { color: var(--muted); font-size: 0.75rem; margin-top: 6px; text-align: right; }
54
+ `