create-theokit 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +9 -7
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
- package/templates/default/app/error.tsx +11 -0
- package/templates/default/app/layout.tsx +3 -42
- package/templates/default/app/loading.tsx +10 -0
- package/templates/default/app/not-found.tsx +13 -0
- package/templates/default/app/page.tsx +31 -143
- package/templates/default/app.tsx +25 -0
- package/templates/default/package.json.tmpl +9 -5
- package/templates/default/public/client.js +173 -0
- package/templates/default/public/favicon.svg +1 -0
- package/templates/default/public/globals.css +206 -0
- package/templates/default/public/robots.txt +2 -0
- package/templates/default/tsconfig.json +1 -0
- package/templates/default/app.ts +0 -24
- package/templates/default/public/index.html +0 -70
package/dist/cli.js
CHANGED
|
@@ -604,8 +604,10 @@ function applyOptions(targetDir, options, opts) {
|
|
|
604
604
|
pkg.devDependencies.tailwindcss = "^4.0.0";
|
|
605
605
|
pkg.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
606
606
|
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
607
|
-
const cssDir = existsSync4(resolve3(targetDir, "src/
|
|
608
|
-
|
|
607
|
+
const cssDir = existsSync4(resolve3(targetDir, "src/public")) ? "src/public" : "public";
|
|
608
|
+
const cssPath = resolve3(targetDir, `${cssDir}/globals.css`);
|
|
609
|
+
const existing = existsSync4(cssPath) ? readFileSync4(cssPath, "utf-8") : "";
|
|
610
|
+
writeFileSync4(cssPath, '@import "tailwindcss";\n\n' + existing);
|
|
609
611
|
}
|
|
610
612
|
if (options.srcDir) {
|
|
611
613
|
const srcDir = resolve3(targetDir, "src");
|
|
@@ -615,17 +617,17 @@ function applyOptions(targetDir, options, opts) {
|
|
|
615
617
|
const to = resolve3(srcDir, dir);
|
|
616
618
|
if (existsSync4(from)) renameSync2(from, to);
|
|
617
619
|
}
|
|
618
|
-
const appFrom = resolve3(targetDir, "app.
|
|
619
|
-
if (existsSync4(appFrom)) renameSync2(appFrom, resolve3(srcDir, "app.
|
|
620
|
+
const appFrom = resolve3(targetDir, "app.tsx");
|
|
621
|
+
if (existsSync4(appFrom)) renameSync2(appFrom, resolve3(srcDir, "app.tsx"));
|
|
620
622
|
const tscPath2 = resolve3(targetDir, "tsconfig.json");
|
|
621
623
|
const tsc2 = JSON.parse(readFileSync4(tscPath2, "utf-8"));
|
|
622
624
|
tsc2.compilerOptions.baseUrl = "src";
|
|
623
625
|
tsc2.include = ["src/**/*.ts", "src/**/*.tsx"];
|
|
624
626
|
writeFileSync4(tscPath2, JSON.stringify(tsc2, null, 2) + "\n");
|
|
625
627
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
626
|
-
pkg.scripts.dev = "npx tsx --watch src/app.
|
|
627
|
-
pkg.scripts["dev:bun"] = "bun --watch src/app.
|
|
628
|
-
pkg.scripts.build = "npx tsup src/app.
|
|
628
|
+
pkg.scripts.dev = "npx tsx --watch src/app.tsx";
|
|
629
|
+
pkg.scripts["dev:bun"] = "bun --watch src/app.tsx";
|
|
630
|
+
pkg.scripts.build = "npx tsup src/app.tsx --format esm --out-dir dist";
|
|
629
631
|
writeFileSync4(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
630
632
|
}
|
|
631
633
|
const tscPath = resolve3(targetDir, "tsconfig.json");
|
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/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"]}
|
|
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/public')) ? 'src/public' : 'public'\n const cssPath = resolve(targetDir, `${cssDir}/globals.css`)\n const existing = existsSync(cssPath) ? readFileSync(cssPath, 'utf-8') : ''\n writeFileSync(cssPath, '@import \"tailwindcss\";\\n\\n' + existing)\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.tsx')\n if (existsSync(appFrom)) renameSync(appFrom, resolve(srcDir, 'app.tsx'))\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.tsx'\n pkg.scripts['dev:bun'] = 'bun --watch src/app.tsx'\n pkg.scripts.build = 'npx tsup src/app.tsx --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
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-theokit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Scaffold a new TheoKit project",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsup"
|
|
8
|
+
"build": "tsup",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"test:watch": "vitest"
|
|
9
11
|
},
|
|
10
12
|
"bin": {
|
|
11
13
|
"create-theokit": "./dist/cli.js"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default function ErrorPage() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="container" style={{ textAlign: 'center', paddingTop: '20vh' }}>
|
|
4
|
+
<h1 style={{ fontSize: '2rem', marginBottom: '12px' }}>Something went wrong</h1>
|
|
5
|
+
<p className="subtitle">An unexpected error occurred.</p>
|
|
6
|
+
<a href="/" style={{ color: 'var(--accent)', marginTop: '16px', display: 'inline-block' }}>
|
|
7
|
+
Go back home
|
|
8
|
+
</a>
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -5,50 +5,11 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|
|
5
5
|
<meta charSet="utf-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
7
7
|
<title>TheoKit App</title>
|
|
8
|
-
<
|
|
8
|
+
<meta name="description" content="Built with TheoKit — the app your agent lives in" />
|
|
9
|
+
<link rel="stylesheet" href="/globals.css" />
|
|
10
|
+
<link rel="icon" href="/favicon.svg" />
|
|
9
11
|
</head>
|
|
10
12
|
<body>{children}</body>
|
|
11
13
|
</html>
|
|
12
14
|
)
|
|
13
15
|
}
|
|
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
|
-
`
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default function NotFound() {
|
|
2
|
+
return (
|
|
3
|
+
<div className="container" style={{ textAlign: 'center', paddingTop: '20vh' }}>
|
|
4
|
+
<h1 style={{ fontSize: '4rem', fontWeight: 700, color: 'var(--text-muted)' }}>404</h1>
|
|
5
|
+
<p className="subtitle" style={{ fontSize: '1.1rem' }}>
|
|
6
|
+
Page not found
|
|
7
|
+
</p>
|
|
8
|
+
<a href="/" style={{ color: 'var(--accent)', marginTop: '16px', display: 'inline-block' }}>
|
|
9
|
+
Go back home
|
|
10
|
+
</a>
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main page — Task Manager + AI Chat.
|
|
3
|
-
*
|
|
4
|
-
* Split layout: left side CRUD, right side AI agent chat.
|
|
5
|
-
* SSE streaming renders token-by-token with tool call visualization.
|
|
6
|
-
*/
|
|
7
1
|
export default function Page() {
|
|
8
2
|
return (
|
|
9
|
-
<div
|
|
3
|
+
<div className="container">
|
|
10
4
|
<header>
|
|
11
|
-
<h1
|
|
12
|
-
|
|
5
|
+
<h1>
|
|
6
|
+
<span className="accent">TheoKit</span> Task Manager
|
|
7
|
+
</h1>
|
|
8
|
+
<p className="subtitle">
|
|
9
|
+
HTTP Controllers + AI Agent — same guards, distinct pipelines. Edit <code>server/</code>{' '}
|
|
10
|
+
to get started.
|
|
11
|
+
</p>
|
|
13
12
|
<div className="role-bar">
|
|
14
|
-
<label>Role
|
|
15
|
-
<select id="role">
|
|
13
|
+
<label htmlFor="role">Role:</label>
|
|
14
|
+
<select id="role" defaultValue="user">
|
|
16
15
|
<option value="">None (public only)</option>
|
|
17
|
-
<option value="user"
|
|
16
|
+
<option value="user">User</option>
|
|
18
17
|
<option value="admin">Admin</option>
|
|
19
18
|
</select>
|
|
20
19
|
</div>
|
|
@@ -23,10 +22,18 @@ export default function Page() {
|
|
|
23
22
|
<main className="grid">
|
|
24
23
|
{/* Left: CRUD Panel */}
|
|
25
24
|
<section className="card">
|
|
26
|
-
<h2
|
|
25
|
+
<h2>
|
|
26
|
+
Tasks <span className="badge">@Controller</span>
|
|
27
|
+
</h2>
|
|
27
28
|
<table>
|
|
28
|
-
<thead
|
|
29
|
-
|
|
29
|
+
<thead>
|
|
30
|
+
<tr>
|
|
31
|
+
<th>Task</th>
|
|
32
|
+
<th>Priority</th>
|
|
33
|
+
<th>Status</th>
|
|
34
|
+
</tr>
|
|
35
|
+
</thead>
|
|
36
|
+
<tbody id="task-list" />
|
|
30
37
|
</table>
|
|
31
38
|
<form id="create-form" className="create-bar">
|
|
32
39
|
<input id="new-title" placeholder="New task..." required minLength={3} />
|
|
@@ -37,147 +44,28 @@ export default function Page() {
|
|
|
37
44
|
</select>
|
|
38
45
|
<button type="submit">Add</button>
|
|
39
46
|
</form>
|
|
40
|
-
<p id="form-error" className="error"
|
|
47
|
+
<p id="form-error" className="error" />
|
|
41
48
|
</section>
|
|
42
49
|
|
|
43
50
|
{/* Right: AI Chat */}
|
|
44
51
|
<section className="card">
|
|
45
|
-
<h2
|
|
52
|
+
<h2>
|
|
53
|
+
AI Assistant <span className="badge badge-ai">@Agent + SSE</span>
|
|
54
|
+
</h2>
|
|
46
55
|
<div id="chat" className="chat-box">
|
|
47
56
|
<div className="msg system">Ask me to list, create, or complete tasks...</div>
|
|
48
57
|
</div>
|
|
49
58
|
<div className="chat-bar">
|
|
50
59
|
<input id="chat-input" placeholder="Message the AI assistant..." />
|
|
51
|
-
<button id="chat-send"
|
|
60
|
+
<button id="chat-send" type="button">
|
|
61
|
+
Send
|
|
62
|
+
</button>
|
|
52
63
|
</div>
|
|
53
|
-
<p id="chat-cost" className="cost"
|
|
64
|
+
<p id="chat-cost" className="cost" />
|
|
54
65
|
</section>
|
|
55
66
|
</main>
|
|
56
67
|
|
|
57
|
-
<
|
|
68
|
+
<script src="/client.js" defer />
|
|
58
69
|
</div>
|
|
59
70
|
)
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
function ClientScript() {
|
|
63
|
-
return (
|
|
64
|
-
<script dangerouslySetInnerHTML={{ __html: CLIENT_JS }} />
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const CLIENT_JS = `
|
|
69
|
-
const API = '';
|
|
70
|
-
let sessionId = 'session-' + Date.now();
|
|
71
|
-
const getRole = () => document.getElementById('role').value;
|
|
72
|
-
const headers = () => {
|
|
73
|
-
const h = { 'Content-Type': 'application/json' };
|
|
74
|
-
const r = getRole();
|
|
75
|
-
if (r) h['x-role'] = r;
|
|
76
|
-
return h;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
// ─── Tasks CRUD ─────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
async function loadTasks() {
|
|
82
|
-
const res = await fetch(API + '/api/tasks');
|
|
83
|
-
const tasks = await res.json();
|
|
84
|
-
document.getElementById('task-list').innerHTML = tasks.map(t => {
|
|
85
|
-
const statusClass = t.done ? 'done' : 'pending';
|
|
86
|
-
const prioClass = t.priority === 'high' ? 'prio-high' : t.priority === 'low' ? 'prio-low' : 'prio-med';
|
|
87
|
-
return '<tr class="' + statusClass + '"><td>' + (t.done ? '✅ ' : '○ ') + t.title + '</td><td><span class="prio ' + prioClass + '">' + t.priority + '</span></td><td>' + (t.done ? 'Done' : 'To do') + '</td></tr>';
|
|
88
|
-
}).join('');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
document.getElementById('create-form').addEventListener('submit', async (e) => {
|
|
92
|
-
e.preventDefault();
|
|
93
|
-
const title = document.getElementById('new-title').value.trim();
|
|
94
|
-
const priority = document.getElementById('new-priority').value;
|
|
95
|
-
const err = document.getElementById('form-error');
|
|
96
|
-
err.textContent = '';
|
|
97
|
-
if (!title) return;
|
|
98
|
-
const res = await fetch(API + '/api/tasks', { method: 'POST', headers: headers(), body: JSON.stringify({ title, priority }) });
|
|
99
|
-
if (res.status === 403) { err.textContent = '403 — Need User role'; return; }
|
|
100
|
-
if (res.status === 422) { const e = await res.json(); err.textContent = e.error?.issues?.[0]?.message || 'Validation error'; return; }
|
|
101
|
-
if (!res.ok) { err.textContent = 'Error ' + res.status; return; }
|
|
102
|
-
document.getElementById('new-title').value = '';
|
|
103
|
-
loadTasks();
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// ─── AI Chat (SSE) ──────────────────────────────────
|
|
107
|
-
|
|
108
|
-
document.getElementById('chat-send').addEventListener('click', sendChat);
|
|
109
|
-
document.getElementById('chat-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') sendChat(); });
|
|
110
|
-
|
|
111
|
-
async function sendChat() {
|
|
112
|
-
const input = document.getElementById('chat-input');
|
|
113
|
-
const msg = input.value.trim();
|
|
114
|
-
if (!msg) return;
|
|
115
|
-
input.value = '';
|
|
116
|
-
|
|
117
|
-
const chat = document.getElementById('chat');
|
|
118
|
-
chat.innerHTML += '<div class="msg user">You: ' + escapeHtml(msg) + '</div>';
|
|
119
|
-
document.getElementById('chat-send').disabled = true;
|
|
120
|
-
|
|
121
|
-
try {
|
|
122
|
-
const res = await fetch(API + '/api/agents/assistant/chat', {
|
|
123
|
-
method: 'POST', headers: headers(),
|
|
124
|
-
body: JSON.stringify({ message: msg, sessionId })
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
if (res.status === 403) {
|
|
128
|
-
chat.innerHTML += '<div class="msg system">403 — Need User role to chat</div>';
|
|
129
|
-
document.getElementById('chat-send').disabled = false;
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const reader = res.body.getReader();
|
|
134
|
-
const decoder = new TextDecoder();
|
|
135
|
-
let agentDiv = document.createElement('div');
|
|
136
|
-
agentDiv.className = 'msg agent';
|
|
137
|
-
agentDiv.textContent = '';
|
|
138
|
-
chat.appendChild(agentDiv);
|
|
139
|
-
|
|
140
|
-
let buffer = '';
|
|
141
|
-
while (true) {
|
|
142
|
-
const { done, value } = await reader.read();
|
|
143
|
-
if (done) break;
|
|
144
|
-
buffer += decoder.decode(value, { stream: true });
|
|
145
|
-
const lines = buffer.split('\\n');
|
|
146
|
-
buffer = lines.pop() || '';
|
|
147
|
-
for (const line of lines) {
|
|
148
|
-
if (!line.startsWith('data: ')) continue;
|
|
149
|
-
try {
|
|
150
|
-
const ev = JSON.parse(line.slice(6));
|
|
151
|
-
if (ev.type === 'text_delta') {
|
|
152
|
-
agentDiv.innerHTML += formatMarkdown(ev.content);
|
|
153
|
-
} else if (ev.type === 'tool_call') {
|
|
154
|
-
chat.insertBefore(toolMsg('🔧 Calling: ' + ev.toolName), agentDiv);
|
|
155
|
-
} else if (ev.type === 'tool_result') {
|
|
156
|
-
chat.insertBefore(toolMsg('✅ ' + (ev.output || '').substring(0, 80)), agentDiv);
|
|
157
|
-
} else if (ev.type === 'thinking') {
|
|
158
|
-
chat.insertBefore(sysMsg('💭 ' + ev.content), agentDiv);
|
|
159
|
-
} else if (ev.type === 'done') {
|
|
160
|
-
const cost = ev.cost ? ' · $' + ev.cost.toFixed(6) : '';
|
|
161
|
-
document.getElementById('chat-cost').textContent = ev.usage.totalTokens + ' tokens · ' + ev.durationMs + 'ms' + cost;
|
|
162
|
-
} else if (ev.type === 'error') {
|
|
163
|
-
chat.innerHTML += '<div class="msg error">' + ev.message + '</div>';
|
|
164
|
-
}
|
|
165
|
-
} catch {}
|
|
166
|
-
}
|
|
167
|
-
chat.scrollTop = chat.scrollHeight;
|
|
168
|
-
}
|
|
169
|
-
loadTasks(); // refresh after agent actions
|
|
170
|
-
} catch (e) {
|
|
171
|
-
chat.innerHTML += '<div class="msg error">Error: ' + e.message + '</div>';
|
|
172
|
-
}
|
|
173
|
-
document.getElementById('chat-send').disabled = false;
|
|
174
|
-
chat.scrollTop = chat.scrollHeight;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function toolMsg(text) { const d = document.createElement('div'); d.className = 'msg tool'; d.textContent = text; return d; }
|
|
178
|
-
function sysMsg(text) { const d = document.createElement('div'); d.className = 'msg system'; d.textContent = text; return d; }
|
|
179
|
-
function escapeHtml(s) { return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
|
180
|
-
function formatMarkdown(s) { return escapeHtml(s).replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>').replace(/\\n/g, '<br>'); }
|
|
181
|
-
|
|
182
|
-
loadTasks();
|
|
183
|
-
`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TheoKit App — opinionated, React-first, full-stack AI agents.
|
|
3
|
+
*
|
|
4
|
+
* To customize: edit app/layout.tsx (head, title, meta)
|
|
5
|
+
* edit app/page.tsx (page content)
|
|
6
|
+
* edit server/ (controllers, agents, toolboxes)
|
|
7
|
+
*/
|
|
8
|
+
import 'reflect-metadata'
|
|
9
|
+
import { TheoApp } from '@theokit/http/app'
|
|
10
|
+
import { TasksController, AssistantAgent, TaskTools } from './server/index.js'
|
|
11
|
+
import Layout from './app/layout.js'
|
|
12
|
+
import Page from './app/page.js'
|
|
13
|
+
|
|
14
|
+
const app = await TheoApp.create({
|
|
15
|
+
controllers: [TasksController],
|
|
16
|
+
agents: [AssistantAgent],
|
|
17
|
+
providers: [TaskTools],
|
|
18
|
+
root: (
|
|
19
|
+
<Layout>
|
|
20
|
+
<Page />
|
|
21
|
+
</Layout>
|
|
22
|
+
),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
await app.listen(3000)
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"dev": "npx tsx --watch app.
|
|
8
|
-
"dev:bun": "bun --watch app.
|
|
9
|
-
"build": "npx tsup app.
|
|
7
|
+
"dev": "npx tsx --watch app.tsx",
|
|
8
|
+
"dev:bun": "bun --watch app.tsx",
|
|
9
|
+
"build": "npx tsup app.tsx --format esm --out-dir dist",
|
|
10
10
|
"start": "node dist/app.js",
|
|
11
11
|
"lint": "eslint .",
|
|
12
12
|
"lint:fix": "eslint . --fix",
|
|
@@ -15,13 +15,17 @@
|
|
|
15
15
|
"typecheck": "tsc --noEmit"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@theokit/http": "^0.
|
|
19
|
-
"@theokit/agents": "^0.
|
|
18
|
+
"@theokit/http": "^0.5.0",
|
|
19
|
+
"@theokit/agents": "^0.4.0",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0",
|
|
20
22
|
"reflect-metadata": "^0.2.0",
|
|
21
23
|
"zod": "^4.0.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"@swc/core": "^1.3.0",
|
|
27
|
+
"@types/react": "^19.0.0",
|
|
28
|
+
"@types/react-dom": "^19.0.0",
|
|
25
29
|
"eslint": "^9.0.0",
|
|
26
30
|
"eslint-config-prettier": "^10.0.0",
|
|
27
31
|
"prettier": "^3.0.0",
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side interactivity — loaded via <script src="/client.js" defer> in page.tsx.
|
|
3
|
+
*
|
|
4
|
+
* Handles: task CRUD via fetch, AI chat via SSE streaming.
|
|
5
|
+
* No build step required — runs directly in the browser.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var getRole = function () {
|
|
9
|
+
return document.getElementById('role').value
|
|
10
|
+
}
|
|
11
|
+
var headers = function () {
|
|
12
|
+
var h = { 'Content-Type': 'application/json' }
|
|
13
|
+
var r = getRole()
|
|
14
|
+
if (r) h['x-role'] = r
|
|
15
|
+
return h
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ─── Tasks CRUD ─────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
async function loadTasks() {
|
|
21
|
+
var res = await fetch('/api/tasks')
|
|
22
|
+
var tasks = await res.json()
|
|
23
|
+
var tbody = document.getElementById('task-list')
|
|
24
|
+
tbody.innerHTML = tasks
|
|
25
|
+
.map(function (t) {
|
|
26
|
+
var cls = t.done ? 'done' : ''
|
|
27
|
+
var prio =
|
|
28
|
+
t.priority === 'high' ? 'prio-high' : t.priority === 'low' ? 'prio-low' : 'prio-med'
|
|
29
|
+
var icon = t.done ? '\u2705 ' : '\u25CB '
|
|
30
|
+
return (
|
|
31
|
+
'<tr class="' +
|
|
32
|
+
cls +
|
|
33
|
+
'"><td>' +
|
|
34
|
+
icon +
|
|
35
|
+
esc(t.title) +
|
|
36
|
+
'</td><td><span class="prio ' +
|
|
37
|
+
prio +
|
|
38
|
+
'">' +
|
|
39
|
+
t.priority +
|
|
40
|
+
'</span></td><td>' +
|
|
41
|
+
(t.done ? 'Done' : 'To do') +
|
|
42
|
+
'</td></tr>'
|
|
43
|
+
)
|
|
44
|
+
})
|
|
45
|
+
.join('')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
document.getElementById('create-form').addEventListener('submit', async function (e) {
|
|
49
|
+
e.preventDefault()
|
|
50
|
+
var input = document.getElementById('new-title')
|
|
51
|
+
var select = document.getElementById('new-priority')
|
|
52
|
+
var err = document.getElementById('form-error')
|
|
53
|
+
err.textContent = ''
|
|
54
|
+
var title = input.value.trim()
|
|
55
|
+
if (!title) return
|
|
56
|
+
var res = await fetch('/api/tasks', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: headers(),
|
|
59
|
+
body: JSON.stringify({ title: title, priority: select.value }),
|
|
60
|
+
})
|
|
61
|
+
if (res.status === 403) {
|
|
62
|
+
err.textContent = '403 \u2014 Need User role'
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
var data = await res.json()
|
|
67
|
+
err.textContent =
|
|
68
|
+
(data.error && data.error.issues && data.error.issues[0] && data.error.issues[0].message) ||
|
|
69
|
+
'Error ' + res.status
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
input.value = ''
|
|
73
|
+
loadTasks()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// ─── AI Chat (SSE) ──────────────────────────────────
|
|
77
|
+
|
|
78
|
+
var sessionId = 'session-' + Date.now()
|
|
79
|
+
var chatEl = document.getElementById('chat')
|
|
80
|
+
var chatInput = document.getElementById('chat-input')
|
|
81
|
+
var chatBtn = document.getElementById('chat-send')
|
|
82
|
+
|
|
83
|
+
chatBtn.addEventListener('click', sendChat)
|
|
84
|
+
chatInput.addEventListener('keydown', function (e) {
|
|
85
|
+
if (e.key === 'Enter') sendChat()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
async function sendChat() {
|
|
89
|
+
var msg = chatInput.value.trim()
|
|
90
|
+
if (!msg) return
|
|
91
|
+
chatInput.value = ''
|
|
92
|
+
chatEl.innerHTML += '<div class="msg user">You: ' + esc(msg) + '</div>'
|
|
93
|
+
chatBtn.disabled = true
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
var res = await fetch('/api/agents/assistant/chat', {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: headers(),
|
|
99
|
+
body: JSON.stringify({ message: msg, sessionId: sessionId }),
|
|
100
|
+
})
|
|
101
|
+
if (res.status === 403) {
|
|
102
|
+
chatEl.innerHTML += '<div class="msg system">403 \u2014 Need User role to chat</div>'
|
|
103
|
+
chatBtn.disabled = false
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var reader = res.body.getReader()
|
|
108
|
+
var decoder = new TextDecoder()
|
|
109
|
+
var agentDiv = document.createElement('div')
|
|
110
|
+
agentDiv.className = 'msg agent'
|
|
111
|
+
chatEl.appendChild(agentDiv)
|
|
112
|
+
|
|
113
|
+
var buf = ''
|
|
114
|
+
while (true) {
|
|
115
|
+
var chunk = await reader.read()
|
|
116
|
+
if (chunk.done) break
|
|
117
|
+
buf += decoder.decode(chunk.value, { stream: true })
|
|
118
|
+
var lines = buf.split('\n')
|
|
119
|
+
buf = lines.pop() || ''
|
|
120
|
+
for (var i = 0; i < lines.length; i++) {
|
|
121
|
+
var line = lines[i]
|
|
122
|
+
if (!line.startsWith('data: ')) continue
|
|
123
|
+
try {
|
|
124
|
+
var ev = JSON.parse(line.slice(6))
|
|
125
|
+
if (ev.type === 'text_delta') agentDiv.innerHTML += fmtMd(ev.content)
|
|
126
|
+
else if (ev.type === 'tool_call')
|
|
127
|
+
chatEl.insertBefore(mkMsg('tool', '\uD83D\uDD27 ' + ev.toolName), agentDiv)
|
|
128
|
+
else if (ev.type === 'tool_result')
|
|
129
|
+
chatEl.insertBefore(
|
|
130
|
+
mkMsg('tool', '\u2705 ' + (ev.output || '').substring(0, 80)),
|
|
131
|
+
agentDiv,
|
|
132
|
+
)
|
|
133
|
+
else if (ev.type === 'thinking')
|
|
134
|
+
chatEl.insertBefore(mkMsg('system', '\uD83D\uDCAD ' + ev.content), agentDiv)
|
|
135
|
+
else if (ev.type === 'done')
|
|
136
|
+
document.getElementById('chat-cost').textContent =
|
|
137
|
+
((ev.usage && ev.usage.totalTokens) || 0) +
|
|
138
|
+
' tokens \u00B7 ' +
|
|
139
|
+
ev.durationMs +
|
|
140
|
+
'ms' +
|
|
141
|
+
(ev.cost ? ' \u00B7 $' + ev.cost.toFixed(6) : '')
|
|
142
|
+
else if (ev.type === 'error')
|
|
143
|
+
chatEl.innerHTML += '<div class="msg error">' + esc(ev.message) + '</div>'
|
|
144
|
+
} catch (_) {
|
|
145
|
+
/* partial JSON */
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
chatEl.scrollTop = chatEl.scrollHeight
|
|
149
|
+
}
|
|
150
|
+
loadTasks()
|
|
151
|
+
} catch (e) {
|
|
152
|
+
chatEl.innerHTML += '<div class="msg error">Error: ' + esc(e.message) + '</div>'
|
|
153
|
+
}
|
|
154
|
+
chatBtn.disabled = false
|
|
155
|
+
chatEl.scrollTop = chatEl.scrollHeight
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function mkMsg(cls, text) {
|
|
159
|
+
var d = document.createElement('div')
|
|
160
|
+
d.className = 'msg ' + cls
|
|
161
|
+
d.textContent = text
|
|
162
|
+
return d
|
|
163
|
+
}
|
|
164
|
+
function esc(s) {
|
|
165
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
166
|
+
}
|
|
167
|
+
function fmtMd(s) {
|
|
168
|
+
return esc(s)
|
|
169
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
170
|
+
.replace(/\n/g, '<br>')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
loadTasks()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="6" fill="#6366f1"/><text x="50%" y="55%" font-family="system-ui" font-size="20" font-weight="bold" fill="white" text-anchor="middle" dominant-baseline="middle">T</text></svg>
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/* TheoKit — global design tokens + reset */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
|
|
5
|
+
--font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;
|
|
6
|
+
--bg: #ffffff;
|
|
7
|
+
--bg-secondary: #fafafa;
|
|
8
|
+
--card: #ffffff;
|
|
9
|
+
--border: #e5e5e5;
|
|
10
|
+
--text: #171717;
|
|
11
|
+
--text-secondary: #666666;
|
|
12
|
+
--text-muted: #999999;
|
|
13
|
+
--accent: #6366f1;
|
|
14
|
+
--accent-hover: #4f46e5;
|
|
15
|
+
--green: #22c55e;
|
|
16
|
+
--red: #ef4444;
|
|
17
|
+
--yellow: #eab308;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@media (prefers-color-scheme: dark) {
|
|
21
|
+
:root {
|
|
22
|
+
--bg: #0a0a0a;
|
|
23
|
+
--bg-secondary: #111111;
|
|
24
|
+
--card: #141414;
|
|
25
|
+
--border: #2a2a2a;
|
|
26
|
+
--text: #ededed;
|
|
27
|
+
--text-secondary: #999999;
|
|
28
|
+
--text-muted: #666666;
|
|
29
|
+
--accent: #818cf8;
|
|
30
|
+
--accent-hover: #6366f1;
|
|
31
|
+
--green: #22c55e;
|
|
32
|
+
--red: #ef4444;
|
|
33
|
+
--yellow: #eab308;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
html { color-scheme: dark; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
40
|
+
|
|
41
|
+
html, body {
|
|
42
|
+
max-width: 100vw;
|
|
43
|
+
overflow-x: hidden;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
body {
|
|
47
|
+
font-family: var(--font-sans);
|
|
48
|
+
background: var(--bg);
|
|
49
|
+
color: var(--text);
|
|
50
|
+
min-height: 100vh;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
-webkit-font-smoothing: antialiased;
|
|
54
|
+
-moz-osx-font-smoothing: grayscale;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
code, pre { font-family: var(--font-mono); }
|
|
58
|
+
|
|
59
|
+
a { color: inherit; text-decoration: none; }
|
|
60
|
+
|
|
61
|
+
/* ─── Layout ────────────────────────────────────────── */
|
|
62
|
+
|
|
63
|
+
.container {
|
|
64
|
+
max-width: 1200px;
|
|
65
|
+
margin: 0 auto;
|
|
66
|
+
padding: 24px;
|
|
67
|
+
flex: 1;
|
|
68
|
+
width: 100%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* ─── Cards ─────────────────────────────────────────── */
|
|
72
|
+
|
|
73
|
+
.card {
|
|
74
|
+
background: var(--card);
|
|
75
|
+
border: 1px solid var(--border);
|
|
76
|
+
border-radius: 12px;
|
|
77
|
+
padding: 24px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* ─── Grid ──────────────────────────────────────────── */
|
|
81
|
+
|
|
82
|
+
.grid {
|
|
83
|
+
display: grid;
|
|
84
|
+
grid-template-columns: 1fr 1fr;
|
|
85
|
+
gap: 24px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media (max-width: 768px) {
|
|
89
|
+
.grid { grid-template-columns: 1fr; }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* ─── Typography ────────────────────────────────────── */
|
|
93
|
+
|
|
94
|
+
h1 { font-size: 1.75rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1.2; }
|
|
95
|
+
h2 { font-size: 1rem; font-weight: 600; margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }
|
|
96
|
+
|
|
97
|
+
.accent { color: var(--accent); }
|
|
98
|
+
.subtitle { color: var(--text-secondary); font-size: 0.9rem; margin-top: 4px; line-height: 1.5; }
|
|
99
|
+
|
|
100
|
+
/* ─── Badges ────────────────────────────────────────── */
|
|
101
|
+
|
|
102
|
+
.badge {
|
|
103
|
+
font-size: 0.65rem;
|
|
104
|
+
padding: 2px 10px;
|
|
105
|
+
border-radius: 99px;
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
|
108
|
+
color: var(--accent);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.badge-ai {
|
|
112
|
+
background: color-mix(in srgb, var(--yellow) 12%, transparent);
|
|
113
|
+
color: var(--yellow);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ─── Table ─────────────────────────────────────────── */
|
|
117
|
+
|
|
118
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
|
|
119
|
+
th { text-align: left; padding: 10px 8px; color: var(--text-muted); border-bottom: 1px solid var(--border); font-weight: 500; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
120
|
+
td { padding: 10px 8px; border-bottom: 1px solid var(--border); }
|
|
121
|
+
tr.done td { opacity: 0.45; text-decoration: line-through; }
|
|
122
|
+
|
|
123
|
+
.prio { font-size: 0.7rem; padding: 2px 10px; border-radius: 99px; font-weight: 500; }
|
|
124
|
+
.prio-high { background: color-mix(in srgb, var(--red) 12%, transparent); color: var(--red); }
|
|
125
|
+
.prio-med { background: color-mix(in srgb, var(--yellow) 12%, transparent); color: var(--yellow); }
|
|
126
|
+
.prio-low { background: color-mix(in srgb, var(--green) 12%, transparent); color: var(--green); }
|
|
127
|
+
|
|
128
|
+
/* ─── Forms ─────────────────────────────────────────── */
|
|
129
|
+
|
|
130
|
+
.create-bar { display: flex; gap: 8px; margin-top: 16px; }
|
|
131
|
+
.create-bar input {
|
|
132
|
+
flex: 1; padding: 10px 14px;
|
|
133
|
+
background: var(--bg-secondary); border: 1px solid var(--border);
|
|
134
|
+
border-radius: 8px; color: var(--text); font-size: 0.85rem;
|
|
135
|
+
outline: none; transition: border-color 0.2s;
|
|
136
|
+
}
|
|
137
|
+
.create-bar input:focus { border-color: var(--accent); }
|
|
138
|
+
.create-bar select {
|
|
139
|
+
padding: 10px 12px; background: var(--bg-secondary);
|
|
140
|
+
border: 1px solid var(--border); border-radius: 8px;
|
|
141
|
+
color: var(--text); font-size: 0.8rem;
|
|
142
|
+
}
|
|
143
|
+
.create-bar button, .chat-bar button {
|
|
144
|
+
padding: 10px 20px; background: var(--accent); color: white;
|
|
145
|
+
border: none; border-radius: 8px; cursor: pointer;
|
|
146
|
+
font-weight: 600; font-size: 0.85rem; transition: background 0.2s;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (hover: hover) and (pointer: fine) {
|
|
150
|
+
.create-bar button:hover, .chat-bar button:hover { background: var(--accent-hover); }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.create-bar button:disabled, .chat-bar button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
154
|
+
.error { color: var(--red); font-size: 0.8rem; margin-top: 4px; }
|
|
155
|
+
|
|
156
|
+
/* ─── Role selector ─────────────────────────────────── */
|
|
157
|
+
|
|
158
|
+
.role-bar { margin-top: 14px; display: flex; align-items: center; gap: 8px; }
|
|
159
|
+
.role-bar label { font-size: 0.8rem; color: var(--text-muted); }
|
|
160
|
+
.role-bar select {
|
|
161
|
+
padding: 6px 12px; background: var(--bg-secondary);
|
|
162
|
+
border: 1px solid var(--border); border-radius: 6px;
|
|
163
|
+
color: var(--text); font-size: 0.8rem;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ─── Chat ──────────────────────────────────────────── */
|
|
167
|
+
|
|
168
|
+
.chat-box {
|
|
169
|
+
height: 420px; overflow-y: auto; padding: 16px;
|
|
170
|
+
background: var(--bg-secondary); border: 1px solid var(--border);
|
|
171
|
+
border-radius: 10px; margin-bottom: 12px;
|
|
172
|
+
font-size: 0.85rem; line-height: 1.7;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.msg { margin-bottom: 10px; padding: 10px 14px; border-radius: 10px; }
|
|
176
|
+
.msg.user { background: color-mix(in srgb, var(--accent) 10%, transparent); color: var(--accent); }
|
|
177
|
+
.msg.agent { background: var(--card); border: 1px solid var(--border); }
|
|
178
|
+
.msg.tool { background: color-mix(in srgb, var(--yellow) 8%, transparent); color: var(--yellow); font-size: 0.78rem; font-family: var(--font-mono); }
|
|
179
|
+
.msg.system { color: var(--text-muted); font-size: 0.78rem; font-style: italic; }
|
|
180
|
+
.msg.error { color: var(--red); font-size: 0.8rem; }
|
|
181
|
+
|
|
182
|
+
.chat-bar { display: flex; gap: 8px; }
|
|
183
|
+
.chat-bar input {
|
|
184
|
+
flex: 1; padding: 12px 16px;
|
|
185
|
+
background: var(--bg-secondary); border: 1px solid var(--border);
|
|
186
|
+
border-radius: 10px; color: var(--text); font-size: 0.9rem;
|
|
187
|
+
outline: none; transition: border-color 0.2s;
|
|
188
|
+
}
|
|
189
|
+
.chat-bar input:focus { border-color: var(--accent); }
|
|
190
|
+
|
|
191
|
+
.cost { color: var(--text-muted); font-size: 0.75rem; margin-top: 8px; text-align: right; }
|
|
192
|
+
|
|
193
|
+
/* ─── Loading ───────────────────────────────────────── */
|
|
194
|
+
|
|
195
|
+
.loading-spinner {
|
|
196
|
+
width: 40px;
|
|
197
|
+
height: 40px;
|
|
198
|
+
border: 3px solid var(--border);
|
|
199
|
+
border-top-color: var(--accent);
|
|
200
|
+
border-radius: 50%;
|
|
201
|
+
animation: spin 0.8s linear infinite;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@keyframes spin {
|
|
205
|
+
to { transform: rotate(360deg); }
|
|
206
|
+
}
|
package/templates/default/app.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TheoKit App — convention over configuration.
|
|
3
|
-
*
|
|
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.
|
|
8
|
-
*/
|
|
9
|
-
import 'reflect-metadata'
|
|
10
|
-
import { readFileSync } from 'node:fs'
|
|
11
|
-
import { TheoApp } from '@theokit/http/app'
|
|
12
|
-
import { TasksController, AssistantAgent, TaskTools } from './server/index.js'
|
|
13
|
-
|
|
14
|
-
let html: string | undefined
|
|
15
|
-
try { html = readFileSync(new URL('./public/index.html', import.meta.url), 'utf-8') } catch { /* no frontend */ }
|
|
16
|
-
|
|
17
|
-
const app = await TheoApp.create({
|
|
18
|
-
controllers: [TasksController],
|
|
19
|
-
agents: [AssistantAgent],
|
|
20
|
-
providers: [TaskTools],
|
|
21
|
-
html,
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
await app.listen(3000)
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
5
|
-
<title>TheoKit App</title>
|
|
6
|
-
<style>
|
|
7
|
-
:root{--bg:#0a0a0a;--card:#141414;--border:#2a2a2a;--text:#e0e0e0;--muted:#888;--accent:#6366f1;--green:#22c55e;--red:#ef4444;--yellow:#eab308}
|
|
8
|
-
*{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);min-height:100vh}
|
|
9
|
-
#app{max-width:1200px;margin:0 auto;padding:20px}h1{font-size:1.6rem}.accent{color:var(--accent)}.subtitle{color:var(--muted);font-size:.85rem;margin-top:2px}
|
|
10
|
-
.role-bar{margin:12px 0}.role-bar select{padding:6px 12px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem}
|
|
11
|
-
.grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:768px){.grid{grid-template-columns:1fr}}
|
|
12
|
-
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px}
|
|
13
|
-
h2{font-size:1rem;margin-bottom:14px;display:flex;align-items:center;gap:8px}.badge{font-size:.65rem;padding:2px 8px;border-radius:99px;background:#6366f122;color:var(--accent)}.badge-ai{background:#eab30822;color:var(--yellow)}
|
|
14
|
-
table{width:100%;border-collapse:collapse;font-size:.85rem}th{text-align:left;padding:8px 4px;color:var(--muted);border-bottom:1px solid var(--border);font-weight:500}td{padding:8px 4px;border-bottom:1px solid var(--border)}
|
|
15
|
-
tr.done td{opacity:.5;text-decoration:line-through}.prio{font-size:.7rem;padding:2px 8px;border-radius:99px}.prio-high{background:#ef444422;color:var(--red)}.prio-med{background:#eab30822;color:var(--yellow)}.prio-low{background:#22c55e22;color:var(--green)}
|
|
16
|
-
.create-bar{display:flex;gap:8px;margin-top:14px}.create-bar input{flex:1;padding:8px 12px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.85rem}
|
|
17
|
-
.create-bar select{padding:8px;background:#1a1a1a;border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:.8rem}.create-bar button{padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:6px;cursor:pointer;font-weight:600}
|
|
18
|
-
.error{color:var(--red);font-size:.8rem;margin-top:4px}
|
|
19
|
-
.chat-box{height:400px;overflow-y:auto;padding:12px;background:#0d0d0d;border-radius:8px;margin-bottom:10px;font-size:.85rem;line-height:1.6}
|
|
20
|
-
.msg{margin-bottom:10px;padding:8px 12px;border-radius:8px}.msg.user{background:#6366f118;color:var(--accent)}.msg.agent{background:#1a1a1a}.msg.tool{background:#eab30810;color:var(--yellow);font-size:.78rem;font-family:monospace}.msg.system{color:var(--muted);font-size:.78rem;font-style:italic}.msg.error{color:var(--red)}
|
|
21
|
-
.chat-bar{display:flex;gap:8px}.chat-bar input{flex:1;padding:10px 14px;background:#1a1a1a;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:.9rem;outline:none}.chat-bar input:focus{border-color:var(--accent)}
|
|
22
|
-
.chat-bar button{padding:10px 20px;background:var(--accent);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:600}.chat-bar button:disabled{opacity:.4;cursor:not-allowed}
|
|
23
|
-
.cost{color:var(--muted);font-size:.75rem;margin-top:6px;text-align:right}
|
|
24
|
-
</style>
|
|
25
|
-
</head>
|
|
26
|
-
<body>
|
|
27
|
-
<div id="app">
|
|
28
|
-
<header>
|
|
29
|
-
<h1><span class="accent">TheoKit</span> App</h1>
|
|
30
|
-
<p class="subtitle">Controllers + AI Agent — same pipeline</p>
|
|
31
|
-
<div class="role-bar"><label>Role: </label><select id="role"><option value="">None</option><option value="user" selected>User</option><option value="admin">Admin</option></select></div>
|
|
32
|
-
</header>
|
|
33
|
-
<main class="grid">
|
|
34
|
-
<section class="card">
|
|
35
|
-
<h2>📋 Tasks <span class="badge">@Controller</span></h2>
|
|
36
|
-
<table><thead><tr><th>Task</th><th>Priority</th><th>Status</th></tr></thead><tbody id="task-list"></tbody></table>
|
|
37
|
-
<form id="create-form" class="create-bar"><input id="new-title" placeholder="New task..." required minlength="3"><select id="new-priority"><option value="medium">Medium</option><option value="high">High</option><option value="low">Low</option></select><button type="submit">Add</button></form>
|
|
38
|
-
<p id="form-error" class="error"></p>
|
|
39
|
-
</section>
|
|
40
|
-
<section class="card">
|
|
41
|
-
<h2>🤖 AI Assistant <span class="badge badge-ai">@Agent + SSE</span></h2>
|
|
42
|
-
<div id="chat" class="chat-box"><div class="msg system">Ask me to list, create, or complete tasks...</div></div>
|
|
43
|
-
<div class="chat-bar"><input id="chat-input" placeholder="Message the AI assistant..."><button id="chat-send">Send</button></div>
|
|
44
|
-
<p id="chat-cost" class="cost"></p>
|
|
45
|
-
</section>
|
|
46
|
-
</main>
|
|
47
|
-
</div>
|
|
48
|
-
<script>
|
|
49
|
-
const getRole=()=>document.getElementById('role').value
|
|
50
|
-
const headers=()=>{const h={'Content-Type':'application/json'};const r=getRole();if(r)h['x-role']=r;return h}
|
|
51
|
-
let sessionId='s-'+Date.now()
|
|
52
|
-
|
|
53
|
-
async function loadTasks(){const r=await fetch('/api/tasks');const t=await r.json();document.getElementById('task-list').innerHTML=t.map(t=>{const s=t.done?'done':'';const p=t.priority==='high'?'prio-high':t.priority==='low'?'prio-low':'prio-med';return'<tr class="'+s+'"><td>'+(t.done?'✅ ':'○ ')+t.title+'</td><td><span class="prio '+p+'">'+t.priority+'</span></td><td>'+(t.done?'Done':'To do')+'</td></tr>'}).join('')}
|
|
54
|
-
|
|
55
|
-
document.getElementById('create-form').addEventListener('submit',async e=>{e.preventDefault();const t=document.getElementById('new-title').value.trim();const p=document.getElementById('new-priority').value;const err=document.getElementById('form-error');err.textContent='';if(!t)return;const r=await fetch('/api/tasks',{method:'POST',headers:headers(),body:JSON.stringify({title:t,priority:p})});if(r.status===403){err.textContent='403 — Need User role';return}if(r.status===422){const e=await r.json();err.textContent=e.error?.issues?.[0]?.message||'Validation error';return}if(!r.ok){err.textContent='Error '+r.status;return}document.getElementById('new-title').value='';loadTasks()})
|
|
56
|
-
|
|
57
|
-
document.getElementById('chat-send').addEventListener('click',sendChat)
|
|
58
|
-
document.getElementById('chat-input').addEventListener('keydown',e=>{if(e.key==='Enter')sendChat()})
|
|
59
|
-
|
|
60
|
-
async function sendChat(){const input=document.getElementById('chat-input');const msg=input.value.trim();if(!msg)return;input.value='';const chat=document.getElementById('chat');chat.innerHTML+='<div class="msg user">You: '+msg.replace(/</g,'<')+'</div>';document.getElementById('chat-send').disabled=true
|
|
61
|
-
try{const r=await fetch('/api/agents/assistant/chat',{method:'POST',headers:headers(),body:JSON.stringify({message:msg,sessionId})});if(r.status===403){chat.innerHTML+='<div class="msg system">403 — Need User role</div>';document.getElementById('chat-send').disabled=false;return}
|
|
62
|
-
const reader=r.body.getReader();const dec=new TextDecoder();let div=document.createElement('div');div.className='msg agent';chat.appendChild(div);let buf=''
|
|
63
|
-
while(true){const{done,value}=await reader.read();if(done)break;buf+=dec.decode(value,{stream:true});const lines=buf.split('\n');buf=lines.pop()||''
|
|
64
|
-
for(const line of lines){if(!line.startsWith('data: '))continue;try{const ev=JSON.parse(line.slice(6));if(ev.type==='text_delta')div.innerHTML+=ev.content.replace(/</g,'<').replace(/\*\*(.+?)\*\*/g,'<strong>$1</strong>').replace(/\n/g,'<br>');else if(ev.type==='tool_call'){const t=document.createElement('div');t.className='msg tool';t.textContent='🔧 '+ev.toolName;chat.insertBefore(t,div)}else if(ev.type==='tool_result'){const t=document.createElement('div');t.className='msg tool';t.textContent='✅ '+(ev.output||'').substring(0,80);chat.insertBefore(t,div)}else if(ev.type==='done'){document.getElementById('chat-cost').textContent=(ev.usage?.totalTokens||0)+' tokens · '+(ev.durationMs||0)+'ms'+(ev.cost?' · $'+ev.cost.toFixed(6):'')}else if(ev.type==='error'){chat.innerHTML+='<div class="msg error">'+ev.message+'</div>'}}catch{}}
|
|
65
|
-
chat.scrollTop=chat.scrollHeight}loadTasks()}catch(e){chat.innerHTML+='<div class="msg error">'+e.message+'</div>'}document.getElementById('chat-send').disabled=false;chat.scrollTop=chat.scrollHeight}
|
|
66
|
-
|
|
67
|
-
loadTasks()
|
|
68
|
-
</script>
|
|
69
|
-
</body>
|
|
70
|
-
</html>
|