@mikulgohil/ai-kit 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +73 -0
  2. package/commands/accessibility-audit.md +143 -0
  3. package/commands/api-route.md +203 -0
  4. package/commands/commit-msg.md +127 -0
  5. package/commands/dep-check.md +148 -0
  6. package/commands/design-tokens.md +146 -0
  7. package/commands/document.md +175 -0
  8. package/commands/env-setup.md +165 -0
  9. package/commands/error-boundary.md +254 -0
  10. package/commands/extract-hook.md +237 -0
  11. package/commands/figma-to-code.md +152 -0
  12. package/commands/fix-bug.md +112 -0
  13. package/commands/migrate.md +174 -0
  14. package/commands/new-component.md +121 -0
  15. package/commands/new-page.md +113 -0
  16. package/commands/optimize.md +120 -0
  17. package/commands/pre-pr.md +159 -0
  18. package/commands/prompt-help.md +175 -0
  19. package/commands/refactor.md +219 -0
  20. package/commands/responsive-check.md +164 -0
  21. package/commands/review.md +120 -0
  22. package/commands/security-check.md +175 -0
  23. package/commands/sitecore-debug.md +216 -0
  24. package/commands/test.md +154 -0
  25. package/commands/token-tips.md +72 -0
  26. package/commands/type-fix.md +224 -0
  27. package/commands/understand.md +84 -0
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.js +1425 -0
  30. package/dist/index.js.map +1 -0
  31. package/docs-scaffolds/component-doc.md +35 -0
  32. package/docs-scaffolds/decisions-log.md +15 -0
  33. package/docs-scaffolds/mistakes-log.md +15 -0
  34. package/docs-scaffolds/time-log.md +14 -0
  35. package/guides/figma-workflow.md +135 -0
  36. package/guides/getting-started.md +61 -0
  37. package/guides/prompt-playbook.md +64 -0
  38. package/guides/token-saving-tips.md +50 -0
  39. package/guides/when-to-use-ai.md +44 -0
  40. package/package.json +58 -0
  41. package/templates/claude-md/base.md +173 -0
  42. package/templates/claude-md/figma.md +62 -0
  43. package/templates/claude-md/monorepo.md +17 -0
  44. package/templates/claude-md/nextjs-app-router.md +29 -0
  45. package/templates/claude-md/nextjs-pages-router.md +28 -0
  46. package/templates/claude-md/sitecore-xmc.md +46 -0
  47. package/templates/claude-md/tailwind.md +18 -0
  48. package/templates/claude-md/typescript.md +19 -0
  49. package/templates/cursorrules/base.md +84 -0
  50. package/templates/cursorrules/figma.md +32 -0
  51. package/templates/cursorrules/monorepo.md +7 -0
  52. package/templates/cursorrules/nextjs-app-router.md +8 -0
  53. package/templates/cursorrules/nextjs-pages-router.md +7 -0
  54. package/templates/cursorrules/sitecore-xmc.md +9 -0
  55. package/templates/cursorrules/tailwind.md +8 -0
  56. package/templates/cursorrules/typescript.md +8 -0
  57. package/templates/header.md +4 -0
  58. package/templates/token-dashboard.html +732 -0
package/dist/index.js ADDED
@@ -0,0 +1,1425 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/constants.ts
7
+ import { fileURLToPath } from "url";
8
+ import path from "path";
9
+ var __filename = fileURLToPath(import.meta.url);
10
+ var __dirname = path.dirname(__filename);
11
+ var PACKAGE_ROOT = path.resolve(__dirname, "..");
12
+ var TEMPLATES_DIR = path.join(PACKAGE_ROOT, "templates");
13
+ var COMMANDS_DIR = path.join(PACKAGE_ROOT, "commands");
14
+ var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
15
+ var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
16
+ var VERSION = "1.0.0";
17
+ var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
18
+ var GENERATED_FILES = {
19
+ claudeMd: "CLAUDE.md",
20
+ cursorRules: ".cursorrules",
21
+ cursorMdcDir: ".cursor/rules",
22
+ claudeSettings: ".claude/settings.json",
23
+ claudeCommands: ".claude/commands"
24
+ };
25
+
26
+ // src/commands/init.ts
27
+ import path15 from "path";
28
+ import fs5 from "fs-extra";
29
+ import { select, confirm } from "@inquirer/prompts";
30
+ import ora from "ora";
31
+
32
+ // src/scanner/index.ts
33
+ import path10 from "path";
34
+
35
+ // src/utils.ts
36
+ import fs from "fs-extra";
37
+ import chalk from "chalk";
38
+ function readJsonSafe(filePath) {
39
+ try {
40
+ return fs.readJsonSync(filePath);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function fileExists(filePath) {
46
+ return fs.existsSync(filePath);
47
+ }
48
+ function dirExists(dirPath) {
49
+ try {
50
+ return fs.statSync(dirPath).isDirectory();
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+ function readFileSafe(filePath) {
56
+ try {
57
+ return fs.readFileSync(filePath, "utf-8");
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+ function logSuccess(message) {
63
+ console.log(chalk.green("\u2713") + " " + message);
64
+ }
65
+ function logWarning(message) {
66
+ console.log(chalk.yellow("\u26A0") + " " + message);
67
+ }
68
+ function logError(message) {
69
+ console.log(chalk.red("\u2717") + " " + message);
70
+ }
71
+ function logInfo(message) {
72
+ console.log(chalk.blue("\u2139") + " " + message);
73
+ }
74
+ function logSection(title) {
75
+ console.log("\n" + chalk.bold.underline(title));
76
+ }
77
+ var AI_KIT_START = "<!-- AI-KIT:START -->";
78
+ var AI_KIT_END = "<!-- AI-KIT:END -->";
79
+ function mergeWithMarkers(existingContent, newGenerated) {
80
+ const startIdx = existingContent.indexOf(AI_KIT_START);
81
+ const endIdx = existingContent.indexOf(AI_KIT_END);
82
+ if (startIdx === -1 || endIdx === -1) {
83
+ return newGenerated;
84
+ }
85
+ const before = existingContent.substring(0, startIdx);
86
+ const after = existingContent.substring(endIdx + AI_KIT_END.length);
87
+ return `${before}${newGenerated}${after}`;
88
+ }
89
+
90
+ // src/scanner/nextjs.ts
91
+ import path2 from "path";
92
+ function detectNextjs(projectPath, pkg) {
93
+ const deps = {
94
+ ...pkg.dependencies,
95
+ ...pkg.devDependencies
96
+ };
97
+ if (!deps.next) {
98
+ if (deps.react) return { framework: "react" };
99
+ return { framework: "unknown" };
100
+ }
101
+ const nextjsVersion = deps.next.replace(/[\^~>=<]/g, "");
102
+ const hasAppDir = dirExists(path2.join(projectPath, "app")) || dirExists(path2.join(projectPath, "src", "app"));
103
+ const hasPagesDir = dirExists(path2.join(projectPath, "pages")) || dirExists(path2.join(projectPath, "src", "pages"));
104
+ let routerType;
105
+ if (hasAppDir && hasPagesDir) routerType = "hybrid";
106
+ else if (hasAppDir) routerType = "app";
107
+ else if (hasPagesDir) routerType = "pages";
108
+ return { framework: "nextjs", nextjsVersion, routerType };
109
+ }
110
+
111
+ // src/scanner/sitecore.ts
112
+ function detectSitecore(pkg) {
113
+ const deps = {
114
+ ...pkg.dependencies,
115
+ ...pkg.devDependencies
116
+ };
117
+ const jssNextjs = deps["@sitecore-jss/sitecore-jss-nextjs"];
118
+ const jssReact = deps["@sitecore-jss/sitecore-jss-react"];
119
+ const contentSdk = deps["@sitecore-content-sdk/nextjs"];
120
+ if (contentSdk || jssNextjs) {
121
+ const version = (contentSdk || jssNextjs || "").replace(/[\^~>=<]/g, "");
122
+ return { cms: "sitecore-xmc", sitecorejssVersion: version || void 0 };
123
+ }
124
+ if (jssReact) {
125
+ const version = jssReact.replace(/[\^~>=<]/g, "");
126
+ return { cms: "sitecore-jss", sitecorejssVersion: version || void 0 };
127
+ }
128
+ return { cms: "none" };
129
+ }
130
+
131
+ // src/scanner/styling.ts
132
+ import path3 from "path";
133
+ function detectStyling(projectPath, pkg) {
134
+ const deps = {
135
+ ...pkg.dependencies,
136
+ ...pkg.devDependencies
137
+ };
138
+ const styling = [];
139
+ let tailwindVersion;
140
+ if (deps.tailwindcss || deps["@tailwindcss/postcss"]) {
141
+ styling.push("tailwind");
142
+ tailwindVersion = (deps.tailwindcss || deps["@tailwindcss/postcss"] || "").replace(/[\^~>=<]/g, "");
143
+ }
144
+ const hasTailwindConfig = fileExists(path3.join(projectPath, "tailwind.config.js")) || fileExists(path3.join(projectPath, "tailwind.config.ts")) || fileExists(path3.join(projectPath, "tailwind.config.mjs"));
145
+ if (hasTailwindConfig && !styling.includes("tailwind")) {
146
+ styling.push("tailwind");
147
+ }
148
+ if (deps["styled-components"]) styling.push("styled-components");
149
+ if (deps.sass || deps["node-sass"]) styling.push("scss");
150
+ return {
151
+ styling,
152
+ tailwindVersion: tailwindVersion || void 0
153
+ };
154
+ }
155
+
156
+ // src/scanner/typescript.ts
157
+ import path4 from "path";
158
+ function detectTypescript(projectPath) {
159
+ const tsconfigPath = path4.join(projectPath, "tsconfig.json");
160
+ if (!fileExists(tsconfigPath)) {
161
+ return { typescript: false };
162
+ }
163
+ const tsconfig = readJsonSafe(tsconfigPath);
164
+ return {
165
+ typescript: true,
166
+ typescriptStrict: tsconfig?.compilerOptions?.strict ?? false
167
+ };
168
+ }
169
+
170
+ // src/scanner/monorepo.ts
171
+ import path5 from "path";
172
+ function detectMonorepo(projectPath, pkg) {
173
+ if (fileExists(path5.join(projectPath, "turbo.json"))) {
174
+ return { monorepo: true, monorepoTool: "turborepo" };
175
+ }
176
+ if (fileExists(path5.join(projectPath, "nx.json"))) {
177
+ return { monorepo: true, monorepoTool: "nx" };
178
+ }
179
+ if (fileExists(path5.join(projectPath, "lerna.json"))) {
180
+ return { monorepo: true, monorepoTool: "lerna" };
181
+ }
182
+ if (fileExists(path5.join(projectPath, "pnpm-workspace.yaml"))) {
183
+ return { monorepo: true, monorepoTool: "pnpm-workspaces" };
184
+ }
185
+ if (pkg.workspaces) {
186
+ return { monorepo: true };
187
+ }
188
+ return { monorepo: false };
189
+ }
190
+
191
+ // src/scanner/package-manager.ts
192
+ import path6 from "path";
193
+ function detectPackageManager(projectPath) {
194
+ const pkg = readJsonSafe(
195
+ path6.join(projectPath, "package.json")
196
+ );
197
+ if (pkg?.packageManager) {
198
+ if (pkg.packageManager.startsWith("pnpm")) return "pnpm";
199
+ if (pkg.packageManager.startsWith("yarn")) return "yarn";
200
+ if (pkg.packageManager.startsWith("bun")) return "bun";
201
+ return "npm";
202
+ }
203
+ if (fileExists(path6.join(projectPath, "pnpm-lock.yaml"))) return "pnpm";
204
+ if (fileExists(path6.join(projectPath, "yarn.lock"))) return "yarn";
205
+ if (fileExists(path6.join(projectPath, "bun.lockb"))) return "bun";
206
+ if (fileExists(path6.join(projectPath, "bun.lock"))) return "bun";
207
+ return "npm";
208
+ }
209
+
210
+ // src/scanner/figma.ts
211
+ import path7 from "path";
212
+ function detectFigma(projectPath, pkg) {
213
+ return {
214
+ figmaMcp: detectFigmaMcp(projectPath),
215
+ figmaCodeCli: detectFigmaCodeCli(pkg),
216
+ designTokens: detectDesignTokens(projectPath),
217
+ tokenFormat: detectTokenFormat(projectPath),
218
+ visualTests: detectVisualTests(projectPath, pkg)
219
+ };
220
+ }
221
+ function detectFigmaMcp(projectPath) {
222
+ const settingsPaths = [
223
+ path7.join(projectPath, ".claude", "settings.json"),
224
+ path7.join(projectPath, ".claude", "settings.local.json"),
225
+ path7.join(projectPath, ".mcp.json")
226
+ ];
227
+ for (const settingsPath of settingsPaths) {
228
+ const content = readFileSafe(settingsPath);
229
+ if (content && content.toLowerCase().includes("figma")) {
230
+ return true;
231
+ }
232
+ }
233
+ return false;
234
+ }
235
+ function detectFigmaCodeCli(pkg) {
236
+ const deps = {
237
+ ...pkg.dependencies,
238
+ ...pkg.devDependencies
239
+ };
240
+ return Object.keys(deps).some(
241
+ (dep) => dep.startsWith("@figma-code/") || dep === "figma-code-cli"
242
+ );
243
+ }
244
+ function detectDesignTokens(projectPath) {
245
+ const tokenPaths = [
246
+ path7.join(projectPath, "tokens.json"),
247
+ path7.join(projectPath, "tokens"),
248
+ path7.join(projectPath, "design-tokens.json"),
249
+ path7.join(projectPath, "src", "tokens")
250
+ ];
251
+ for (const tokenPath of tokenPaths) {
252
+ if (fileExists(tokenPath)) return true;
253
+ }
254
+ const globalsCss = readFileSafe(
255
+ path7.join(projectPath, "src", "app", "globals.css")
256
+ );
257
+ if (globalsCss && globalsCss.includes("@theme")) return true;
258
+ return false;
259
+ }
260
+ function detectTokenFormat(projectPath) {
261
+ const globalsCss = readFileSafe(
262
+ path7.join(projectPath, "src", "app", "globals.css")
263
+ );
264
+ if (globalsCss && globalsCss.includes("@theme")) return "tailwind-v4";
265
+ const twConfigPaths = [
266
+ path7.join(projectPath, "tailwind.config.ts"),
267
+ path7.join(projectPath, "tailwind.config.js")
268
+ ];
269
+ for (const twPath of twConfigPaths) {
270
+ const content = readFileSafe(twPath);
271
+ if (content && content.includes("theme")) return "tailwind-v3";
272
+ }
273
+ if (globalsCss && globalsCss.includes("--")) return "css-variables";
274
+ return "none";
275
+ }
276
+ function detectVisualTests(projectPath, pkg) {
277
+ const deps = {
278
+ ...pkg.dependencies,
279
+ ...pkg.devDependencies
280
+ };
281
+ const hasPlaywright = "@playwright/test" in deps || "playwright" in deps;
282
+ const hasPlaywrightConfig = fileExists(path7.join(projectPath, "playwright.config.ts")) || fileExists(path7.join(projectPath, "playwright.config.js"));
283
+ return hasPlaywright || hasPlaywrightConfig;
284
+ }
285
+
286
+ // src/scanner/tools.ts
287
+ import path8 from "path";
288
+ function detectTools(projectPath, pkg) {
289
+ const deps = {
290
+ ...pkg.dependencies,
291
+ ...pkg.devDependencies
292
+ };
293
+ return {
294
+ playwright: detectPlaywright(projectPath, deps),
295
+ storybook: detectStorybook(projectPath, deps),
296
+ eslint: detectEslint(projectPath, deps),
297
+ prettier: detectPrettier(projectPath, deps),
298
+ axeCore: detectAxeCore(deps),
299
+ snyk: detectSnyk(projectPath, deps),
300
+ knip: detectKnip(projectPath, deps),
301
+ bundleAnalyzer: detectBundleAnalyzer(deps)
302
+ };
303
+ }
304
+ function detectPlaywright(projectPath, deps) {
305
+ if ("@playwright/test" in deps) return true;
306
+ if (fileExists(path8.join(projectPath, "playwright.config.ts"))) return true;
307
+ if (fileExists(path8.join(projectPath, "playwright.config.js"))) return true;
308
+ return false;
309
+ }
310
+ function detectStorybook(projectPath, deps) {
311
+ if ("@storybook/react" in deps) return true;
312
+ if (dirExists(path8.join(projectPath, ".storybook"))) return true;
313
+ return false;
314
+ }
315
+ function detectEslint(projectPath, deps) {
316
+ if ("eslint" in deps) return true;
317
+ const eslintConfigs = [
318
+ ".eslintrc",
319
+ ".eslintrc.js",
320
+ ".eslintrc.cjs",
321
+ ".eslintrc.json",
322
+ ".eslintrc.yml",
323
+ ".eslintrc.yaml",
324
+ "eslint.config.js",
325
+ "eslint.config.mjs",
326
+ "eslint.config.cjs",
327
+ "eslint.config.ts"
328
+ ];
329
+ for (const config of eslintConfigs) {
330
+ if (fileExists(path8.join(projectPath, config))) return true;
331
+ }
332
+ return false;
333
+ }
334
+ function detectPrettier(projectPath, deps) {
335
+ if ("prettier" in deps) return true;
336
+ const prettierConfigs = [
337
+ ".prettierrc",
338
+ ".prettierrc.js",
339
+ ".prettierrc.cjs",
340
+ ".prettierrc.json",
341
+ ".prettierrc.yml",
342
+ ".prettierrc.yaml",
343
+ ".prettierrc.toml",
344
+ "prettier.config.js",
345
+ "prettier.config.cjs",
346
+ "prettier.config.ts"
347
+ ];
348
+ for (const config of prettierConfigs) {
349
+ if (fileExists(path8.join(projectPath, config))) return true;
350
+ }
351
+ return false;
352
+ }
353
+ function detectAxeCore(deps) {
354
+ return "@axe-core/playwright" in deps || "axe-core" in deps;
355
+ }
356
+ function detectSnyk(projectPath, deps) {
357
+ if ("snyk" in deps) return true;
358
+ if (fileExists(path8.join(projectPath, ".snyk"))) return true;
359
+ return false;
360
+ }
361
+ function detectKnip(projectPath, deps) {
362
+ if ("knip" in deps) return true;
363
+ if (fileExists(path8.join(projectPath, "knip.json"))) return true;
364
+ if (fileExists(path8.join(projectPath, "knip.config.ts"))) return true;
365
+ return false;
366
+ }
367
+ function detectBundleAnalyzer(deps) {
368
+ return "@next/bundle-analyzer" in deps;
369
+ }
370
+
371
+ // src/scanner/mcp.ts
372
+ import path9 from "path";
373
+ function detectMcpServers(projectPath) {
374
+ const settingsPaths = [
375
+ path9.join(projectPath, ".claude", "settings.json"),
376
+ path9.join(projectPath, ".claude", "settings.local.json"),
377
+ path9.join(projectPath, ".mcp.json")
378
+ ];
379
+ let combined = "";
380
+ for (const settingsPath of settingsPaths) {
381
+ const content = readFileSafe(settingsPath);
382
+ if (content) {
383
+ combined += content.toLowerCase() + "\n";
384
+ }
385
+ }
386
+ return {
387
+ playwright: combined.includes("playwright"),
388
+ figma: combined.includes("figma"),
389
+ github: combined.includes("github"),
390
+ context7: combined.includes("context7"),
391
+ perplexity: combined.includes("perplexity"),
392
+ notion: combined.includes("notion")
393
+ };
394
+ }
395
+
396
+ // src/scanner/index.ts
397
+ async function scanProject(projectPath) {
398
+ const pkgPath = path10.join(projectPath, "package.json");
399
+ const pkg = readJsonSafe(pkgPath) || {};
400
+ const scripts = pkg.scripts || {};
401
+ const projectName = pkg.name || path10.basename(projectPath);
402
+ const nextjsResult = detectNextjs(projectPath, pkg);
403
+ const sitecoreResult = detectSitecore(pkg);
404
+ const stylingResult = detectStyling(projectPath, pkg);
405
+ const tsResult = detectTypescript(projectPath);
406
+ const monorepoResult = detectMonorepo(projectPath, pkg);
407
+ const packageManager = detectPackageManager(projectPath);
408
+ const figmaResult = detectFigma(projectPath, pkg);
409
+ const toolsResult = detectTools(projectPath, pkg);
410
+ const mcpResult = detectMcpServers(projectPath);
411
+ const figmaDetected = figmaResult.figmaMcp || figmaResult.figmaCodeCli || figmaResult.designTokens;
412
+ return {
413
+ ...nextjsResult,
414
+ ...sitecoreResult,
415
+ ...stylingResult,
416
+ ...tsResult,
417
+ ...monorepoResult,
418
+ figma: {
419
+ detected: figmaDetected,
420
+ ...figmaResult
421
+ },
422
+ tools: toolsResult,
423
+ mcpServers: mcpResult,
424
+ packageManager,
425
+ projectName,
426
+ projectPath,
427
+ scripts
428
+ };
429
+ }
430
+
431
+ // src/generator/assembler.ts
432
+ import path11 from "path";
433
+ function readTemplate(relativePath) {
434
+ const fullPath = path11.join(TEMPLATES_DIR, relativePath);
435
+ const content = readFileSafe(fullPath);
436
+ if (!content) {
437
+ throw new Error(`Template not found: ${relativePath}`);
438
+ }
439
+ return content.trim();
440
+ }
441
+ function assembleTemplate(subfolder, fragments, variables) {
442
+ const versionComment = `<!-- Generated by ai-kit v${VERSION} -->`;
443
+ const header = readTemplate("header.md");
444
+ const body = fragments.map((name) => readTemplate(`${subfolder}/${name}.md`)).join("\n\n---\n\n");
445
+ const full = `${versionComment}
446
+ ${header}
447
+
448
+ ---
449
+
450
+ ${body}`;
451
+ const replaced = replacePlaceholders(full, variables);
452
+ return `<!-- AI-KIT:START -->
453
+ ${replaced}
454
+ <!-- AI-KIT:END -->`;
455
+ }
456
+ function replacePlaceholders(content, variables) {
457
+ let result = content;
458
+ for (const [key, value] of Object.entries(variables)) {
459
+ result = result.replaceAll(`{{${key}}}`, value);
460
+ }
461
+ return result;
462
+ }
463
+
464
+ // src/generator/claude-md.ts
465
+ function selectFragments(scan) {
466
+ const fragments = ["base"];
467
+ if (scan.framework === "nextjs") {
468
+ if (scan.routerType === "app" || scan.routerType === "hybrid") {
469
+ fragments.push("nextjs-app-router");
470
+ }
471
+ if (scan.routerType === "pages" || scan.routerType === "hybrid") {
472
+ fragments.push("nextjs-pages-router");
473
+ }
474
+ }
475
+ if (scan.cms !== "none") {
476
+ fragments.push("sitecore-xmc");
477
+ }
478
+ if (scan.styling.includes("tailwind")) {
479
+ fragments.push("tailwind");
480
+ }
481
+ if (scan.typescript) {
482
+ fragments.push("typescript");
483
+ }
484
+ if (scan.monorepo) {
485
+ fragments.push("monorepo");
486
+ }
487
+ if (scan.figma?.detected) {
488
+ fragments.push("figma");
489
+ }
490
+ return fragments;
491
+ }
492
+ function generateClaudeMd(scan) {
493
+ const fragments = selectFragments(scan);
494
+ const variables = buildVariables(scan);
495
+ return assembleTemplate("claude-md", fragments, variables);
496
+ }
497
+ function buildVariables(scan) {
498
+ const techStack = [];
499
+ if (scan.framework === "nextjs") {
500
+ techStack.push(`Next.js ${scan.nextjsVersion || ""}`);
501
+ }
502
+ if (scan.cms !== "none") {
503
+ techStack.push(
504
+ scan.cms === "sitecore-xmc" ? `Sitecore XM Cloud${scan.sitecorejssVersion ? ` (JSS ${scan.sitecorejssVersion})` : ""}` : "Sitecore JSS"
505
+ );
506
+ }
507
+ if (scan.typescript) techStack.push("TypeScript");
508
+ if (scan.styling.includes("tailwind"))
509
+ techStack.push(`Tailwind CSS ${scan.tailwindVersion || ""}`);
510
+ if (scan.styling.includes("scss")) techStack.push("SCSS");
511
+ if (scan.styling.includes("styled-components"))
512
+ techStack.push("styled-components");
513
+ if (scan.monorepo && scan.monorepoTool)
514
+ techStack.push(scan.monorepoTool);
515
+ const scripts = Object.entries(scan.scripts).filter(
516
+ ([key]) => ["dev", "build", "start", "lint", "test", "type-check", "typecheck"].includes(key)
517
+ ).map(([key, value]) => `- \`${scan.packageManager} run ${key}\` \u2192 \`${value}\``).join("\n");
518
+ return {
519
+ projectName: scan.projectName,
520
+ techStack: techStack.join(" \xB7 "),
521
+ packageManager: scan.packageManager,
522
+ routerType: scan.routerType || "unknown",
523
+ scripts: scripts || "- No scripts detected",
524
+ framework: scan.framework
525
+ };
526
+ }
527
+
528
+ // src/generator/cursorrules.ts
529
+ function generateCursorRules(scan) {
530
+ const fragments = selectFragments(scan);
531
+ const variables = buildCursorVariables(scan);
532
+ return assembleTemplate("cursorrules", fragments, variables);
533
+ }
534
+ function buildCursorVariables(scan) {
535
+ const techStack = [];
536
+ if (scan.framework === "nextjs") {
537
+ techStack.push(`Next.js ${scan.nextjsVersion || ""}`);
538
+ }
539
+ if (scan.cms !== "none") {
540
+ techStack.push(scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : "Sitecore JSS");
541
+ }
542
+ if (scan.typescript) techStack.push("TypeScript");
543
+ if (scan.styling.includes("tailwind")) techStack.push("Tailwind CSS");
544
+ if (scan.monorepo && scan.monorepoTool) techStack.push(scan.monorepoTool);
545
+ const scripts = Object.entries(scan.scripts).filter(
546
+ ([key]) => ["dev", "build", "start", "lint", "test", "type-check", "typecheck"].includes(key)
547
+ ).map(([key, value]) => `- \`${scan.packageManager} run ${key}\` \u2192 \`${value}\``).join("\n");
548
+ return {
549
+ projectName: scan.projectName,
550
+ techStack: techStack.join(" \xB7 "),
551
+ packageManager: scan.packageManager,
552
+ routerType: scan.routerType || "unknown",
553
+ scripts: scripts || "- No scripts detected",
554
+ framework: scan.framework
555
+ };
556
+ }
557
+
558
+ // src/generator/cursor-mdc.ts
559
+ var MDC_CONFIG = {
560
+ base: {
561
+ description: "Project coding standards and conventions",
562
+ globs: "**/*"
563
+ },
564
+ "nextjs-app-router": {
565
+ description: "Next.js App Router patterns and best practices",
566
+ globs: "app/**/*.{ts,tsx}, src/app/**/*.{ts,tsx}"
567
+ },
568
+ "nextjs-pages-router": {
569
+ description: "Next.js Pages Router patterns and best practices",
570
+ globs: "pages/**/*.{ts,tsx}, src/pages/**/*.{ts,tsx}"
571
+ },
572
+ "sitecore-xmc": {
573
+ description: "Sitecore XM Cloud component patterns and field helpers",
574
+ globs: "src/components/**/*.{ts,tsx}"
575
+ },
576
+ tailwind: {
577
+ description: "Tailwind CSS conventions and utility patterns",
578
+ globs: "**/*.{tsx,jsx,css}"
579
+ },
580
+ typescript: {
581
+ description: "TypeScript strict typing conventions",
582
+ globs: "**/*.{ts,tsx}"
583
+ },
584
+ monorepo: {
585
+ description: "Monorepo workspace conventions",
586
+ globs: "**/*"
587
+ },
588
+ figma: {
589
+ description: "Figma-to-code workflow and design token rules",
590
+ globs: "src/components/**/*.{ts,tsx}, tokens/**/*"
591
+ }
592
+ };
593
+ function generateMdcFiles(scan) {
594
+ const fragments = selectFragments(scan);
595
+ const variables = buildCursorVariables(scan);
596
+ return fragments.map((fragment) => {
597
+ const config = MDC_CONFIG[fragment] || {
598
+ description: fragment,
599
+ globs: "**/*"
600
+ };
601
+ const body = readTemplate(`cursorrules/${fragment}.md`);
602
+ const replaced = replacePlaceholders(body, variables);
603
+ const frontmatter = [
604
+ "---",
605
+ `description: ${config.description}`,
606
+ `globs: ${config.globs}`,
607
+ "alwaysApply: true",
608
+ "---"
609
+ ].join("\n");
610
+ return {
611
+ filename: `${fragment}.mdc`,
612
+ content: `${frontmatter}
613
+
614
+ <!-- Generated by ai-kit v${VERSION} -->
615
+
616
+ ${replaced}`
617
+ };
618
+ });
619
+ }
620
+
621
+ // src/generator/config.ts
622
+ function generateConfig(scan, templates, commands, guides) {
623
+ return {
624
+ version: VERSION,
625
+ scanResult: scan,
626
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
627
+ templates,
628
+ commands,
629
+ guides
630
+ };
631
+ }
632
+
633
+ // src/copier/commands.ts
634
+ import path12 from "path";
635
+ import fs2 from "fs-extra";
636
+ var AVAILABLE_COMMANDS = [
637
+ "prompt-help",
638
+ "review",
639
+ "fix-bug",
640
+ "new-component",
641
+ "new-page",
642
+ "understand",
643
+ "test",
644
+ "optimize",
645
+ "figma-to-code",
646
+ "design-tokens",
647
+ "accessibility-audit",
648
+ "security-check",
649
+ "refactor",
650
+ "api-route",
651
+ "pre-pr",
652
+ "migrate",
653
+ "error-boundary",
654
+ "type-fix",
655
+ "extract-hook",
656
+ "dep-check",
657
+ "env-setup",
658
+ "commit-msg",
659
+ "sitecore-debug",
660
+ "responsive-check",
661
+ "document",
662
+ "token-tips"
663
+ ];
664
+ async function copyCommands(targetDir) {
665
+ const commandsTarget = path12.join(targetDir, ".claude", "commands");
666
+ await fs2.ensureDir(commandsTarget);
667
+ const copied = [];
668
+ for (const cmd of AVAILABLE_COMMANDS) {
669
+ const src = path12.join(COMMANDS_DIR, `${cmd}.md`);
670
+ const dest = path12.join(commandsTarget, `${cmd}.md`);
671
+ if (await fs2.pathExists(src)) {
672
+ await fs2.copy(src, dest, { overwrite: true });
673
+ copied.push(cmd);
674
+ }
675
+ }
676
+ return copied;
677
+ }
678
+
679
+ // src/copier/guides.ts
680
+ import path13 from "path";
681
+ import fs3 from "fs-extra";
682
+ var AVAILABLE_GUIDES = [
683
+ "getting-started",
684
+ "prompt-playbook",
685
+ "when-to-use-ai",
686
+ "token-saving-tips",
687
+ "figma-workflow"
688
+ ];
689
+ async function copyGuides(targetDir) {
690
+ const guidesTarget = path13.join(targetDir, "ai-kit", "guides");
691
+ await fs3.ensureDir(guidesTarget);
692
+ const copied = [];
693
+ for (const guide of AVAILABLE_GUIDES) {
694
+ const src = path13.join(GUIDES_DIR, `${guide}.md`);
695
+ const dest = path13.join(guidesTarget, `${guide}.md`);
696
+ if (await fs3.pathExists(src)) {
697
+ await fs3.copy(src, dest, { overwrite: true });
698
+ copied.push(guide);
699
+ }
700
+ }
701
+ return copied;
702
+ }
703
+
704
+ // src/copier/docs.ts
705
+ import path14 from "path";
706
+ import fs4 from "fs-extra";
707
+ var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
708
+ async function scaffoldDocs(targetDir) {
709
+ const docsTarget = path14.join(targetDir, "docs");
710
+ await fs4.ensureDir(docsTarget);
711
+ const created = [];
712
+ for (const doc of DOC_SCAFFOLDS) {
713
+ const src = path14.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
714
+ const dest = path14.join(docsTarget, `${doc}.md`);
715
+ if (await fs4.pathExists(dest)) {
716
+ continue;
717
+ }
718
+ if (await fs4.pathExists(src)) {
719
+ await fs4.copy(src, dest);
720
+ created.push(doc);
721
+ }
722
+ }
723
+ return created;
724
+ }
725
+
726
+ // src/commands/init.ts
727
+ async function initCommand(targetPath) {
728
+ const projectDir = path15.resolve(targetPath || process.cwd());
729
+ logSection("AI Kit \u2014 Project Setup");
730
+ logInfo(`Scanning: ${projectDir}`);
731
+ const configPath = path15.join(projectDir, AI_KIT_CONFIG_FILE);
732
+ if (fileExists(configPath)) {
733
+ const overwrite = await confirm({
734
+ message: "AI Kit is already configured in this project. Re-initialize?",
735
+ default: false
736
+ });
737
+ if (!overwrite) {
738
+ logInfo("Cancelled. Use `ai-kit update` to refresh configs.");
739
+ return;
740
+ }
741
+ }
742
+ const spinner = ora("Scanning project...").start();
743
+ let scan;
744
+ try {
745
+ scan = await scanProject(projectDir);
746
+ spinner.succeed("Project scanned");
747
+ } catch (err) {
748
+ spinner.fail("Failed to scan project");
749
+ logError(String(err));
750
+ return;
751
+ }
752
+ logSection("Detected Stack");
753
+ logInfo(`Framework: ${formatFramework(scan)}`);
754
+ logInfo(`CMS: ${scan.cms === "none" ? "None" : scan.cms}`);
755
+ logInfo(`Styling: ${scan.styling.join(", ") || "None detected"}`);
756
+ logInfo(`TypeScript: ${scan.typescript ? "Yes" : "No"}`);
757
+ logInfo(`Monorepo: ${scan.monorepo ? `Yes (${scan.monorepoTool})` : "No"}`);
758
+ logInfo(`Package Manager: ${scan.packageManager}`);
759
+ const clarifications = await askClarifications(scan);
760
+ scan = applyClarifications(scan, clarifications);
761
+ const tools = await selectTools();
762
+ const conflict = await selectConflictStrategy(projectDir);
763
+ logSection("Generating Files");
764
+ const results = await generate(projectDir, scan, tools, conflict);
765
+ logSection("Setup Complete");
766
+ if (results.claudeMd) logSuccess(`CLAUDE.md generated`);
767
+ if (results.cursorRules) logSuccess(`.cursorrules generated`);
768
+ if (results.cursorMdcFiles > 0)
769
+ logSuccess(`${results.cursorMdcFiles} .cursor/rules/*.mdc files generated`);
770
+ if (results.commands.length > 0)
771
+ logSuccess(`${results.commands.length} slash commands copied`);
772
+ if (results.guides.length > 0)
773
+ logSuccess(`${results.guides.length} guides added to ai-kit/guides/`);
774
+ if (results.docs.length > 0)
775
+ logSuccess(`${results.docs.length} doc scaffolds created in docs/`);
776
+ showRecommendations(scan);
777
+ console.log("");
778
+ logInfo("Run `ai-kit update` anytime to refresh configs after project changes.");
779
+ logInfo("Check ai-kit/guides/getting-started.md to get started.");
780
+ }
781
+ function formatFramework(scan) {
782
+ if (scan.framework === "nextjs") {
783
+ const version = scan.nextjsVersion ? ` ${scan.nextjsVersion}` : "";
784
+ const router = scan.routerType ? ` (${scan.routerType} router)` : "";
785
+ return `Next.js${version}${router}`;
786
+ }
787
+ return scan.framework;
788
+ }
789
+ async function askClarifications(scan) {
790
+ const answers = {};
791
+ if (scan.framework === "nextjs" && !scan.routerType) {
792
+ answers.routerType = await select({
793
+ message: "Which Next.js router does this project use?",
794
+ choices: [
795
+ { name: "App Router (app/ directory)", value: "app" },
796
+ { name: "Pages Router (pages/ directory)", value: "pages" },
797
+ { name: "Both (hybrid)", value: "hybrid" }
798
+ ]
799
+ });
800
+ }
801
+ return answers;
802
+ }
803
+ function applyClarifications(scan, clarifications) {
804
+ return {
805
+ ...scan,
806
+ ...clarifications.routerType && { routerType: clarifications.routerType },
807
+ ...clarifications.cms && { cms: clarifications.cms }
808
+ };
809
+ }
810
+ async function selectTools() {
811
+ const tool = await select({
812
+ message: "Which AI tools does this project use?",
813
+ choices: [
814
+ { name: "Both Claude Code & Cursor", value: "both" },
815
+ { name: "Claude Code only", value: "claude" },
816
+ { name: "Cursor only", value: "cursor" }
817
+ ],
818
+ default: "both"
819
+ });
820
+ return {
821
+ claude: tool === "both" || tool === "claude",
822
+ cursor: tool === "both" || tool === "cursor"
823
+ };
824
+ }
825
+ async function selectConflictStrategy(projectDir) {
826
+ const hasExisting = fileExists(path15.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path15.join(projectDir, GENERATED_FILES.cursorRules));
827
+ if (!hasExisting) return "overwrite";
828
+ return select({
829
+ message: "Existing AI config files detected. How should we handle conflicts?",
830
+ choices: [
831
+ {
832
+ name: "Overwrite \u2014 replace with fresh generated files",
833
+ value: "overwrite"
834
+ },
835
+ {
836
+ name: "Skip \u2014 keep existing files, only add missing ones",
837
+ value: "skip"
838
+ }
839
+ ],
840
+ default: "overwrite"
841
+ });
842
+ }
843
+ async function generate(projectDir, scan, tools, conflict) {
844
+ const result = {
845
+ claudeMd: false,
846
+ cursorRules: false,
847
+ cursorMdcFiles: 0,
848
+ commands: [],
849
+ guides: [],
850
+ docs: []
851
+ };
852
+ if (tools.claude) {
853
+ const claudeMdPath = path15.join(projectDir, GENERATED_FILES.claudeMd);
854
+ if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
855
+ const content = generateClaudeMd(scan);
856
+ await fs5.writeFile(claudeMdPath, content, "utf-8");
857
+ result.claudeMd = true;
858
+ } else {
859
+ logWarning("CLAUDE.md exists, skipping");
860
+ }
861
+ result.commands = await copyCommands(projectDir);
862
+ await fs5.ensureDir(path15.join(projectDir, ".claude", "commands"));
863
+ }
864
+ if (tools.cursor) {
865
+ const cursorPath = path15.join(projectDir, GENERATED_FILES.cursorRules);
866
+ if (conflict === "overwrite" || !fileExists(cursorPath)) {
867
+ const content = generateCursorRules(scan);
868
+ await fs5.writeFile(cursorPath, content, "utf-8");
869
+ result.cursorRules = true;
870
+ } else {
871
+ logWarning(".cursorrules exists, skipping");
872
+ }
873
+ const mdcDir = path15.join(projectDir, GENERATED_FILES.cursorMdcDir);
874
+ await fs5.ensureDir(mdcDir);
875
+ const mdcFiles = generateMdcFiles(scan);
876
+ for (const mdc of mdcFiles) {
877
+ await fs5.writeFile(path15.join(mdcDir, mdc.filename), mdc.content, "utf-8");
878
+ }
879
+ result.cursorMdcFiles = mdcFiles.length;
880
+ }
881
+ result.guides = await copyGuides(projectDir);
882
+ result.docs = await scaffoldDocs(projectDir);
883
+ const templates = [];
884
+ if (result.claudeMd) templates.push("CLAUDE.md");
885
+ if (result.cursorRules) templates.push(".cursorrules");
886
+ const config = generateConfig(scan, templates, result.commands, result.guides);
887
+ await fs5.writeJson(
888
+ path15.join(projectDir, AI_KIT_CONFIG_FILE),
889
+ config,
890
+ { spaces: 2 }
891
+ );
892
+ return result;
893
+ }
894
+ function showRecommendations(scan) {
895
+ const toolRecs = [
896
+ {
897
+ check: scan.tools.playwright,
898
+ label: "Playwright not detected \u2014 install for E2E testing:",
899
+ hint: " npm install -D @playwright/test && npx playwright install"
900
+ },
901
+ {
902
+ check: scan.tools.eslint,
903
+ label: "ESLint not detected \u2014 install for code quality:",
904
+ hint: " npm install -D eslint @typescript-eslint/eslint-plugin"
905
+ },
906
+ {
907
+ check: scan.tools.prettier,
908
+ label: "Prettier not detected \u2014 install for code formatting:",
909
+ hint: " npm install -D prettier"
910
+ },
911
+ {
912
+ check: scan.tools.axeCore,
913
+ label: "axe-core not detected \u2014 install for accessibility testing:",
914
+ hint: " npm install -D @axe-core/playwright"
915
+ },
916
+ {
917
+ check: scan.tools.knip,
918
+ label: "Knip not detected \u2014 install to find unused code:",
919
+ hint: " npm install -D knip"
920
+ },
921
+ {
922
+ check: scan.tools.bundleAnalyzer,
923
+ label: "Bundle analyzer not detected \u2014 install for bundle insights:",
924
+ hint: " npm install -D @next/bundle-analyzer"
925
+ }
926
+ ];
927
+ const mcpRecs = [
928
+ {
929
+ check: scan.mcpServers.context7,
930
+ label: "Context7 MCP not configured \u2014 enables up-to-date library docs:",
931
+ hint: " Add to .claude/settings.json mcpServers"
932
+ },
933
+ {
934
+ check: scan.mcpServers.playwright,
935
+ label: "Playwright MCP not configured \u2014 enables browser automation:",
936
+ hint: " Add to .claude/settings.json mcpServers"
937
+ },
938
+ {
939
+ check: scan.mcpServers.github,
940
+ label: "GitHub MCP not configured \u2014 enables PR/issue management:",
941
+ hint: " Add to .claude/settings.json mcpServers"
942
+ },
943
+ {
944
+ check: scan.mcpServers.perplexity,
945
+ label: "Perplexity MCP not configured \u2014 enables web research:",
946
+ hint: " Add to .claude/settings.json mcpServers"
947
+ }
948
+ ];
949
+ const missingTools = toolRecs.filter((r) => !r.check);
950
+ const missingMcps = mcpRecs.filter((r) => !r.check);
951
+ if (missingTools.length === 0 && missingMcps.length === 0) return;
952
+ logSection("Recommended Setup");
953
+ for (const rec of missingTools) {
954
+ logInfo(rec.label);
955
+ logInfo(rec.hint);
956
+ }
957
+ for (const rec of missingMcps) {
958
+ logInfo(rec.label);
959
+ logInfo(rec.hint);
960
+ }
961
+ }
962
+
963
+ // src/commands/update.ts
964
+ import path16 from "path";
965
+ import fs6 from "fs-extra";
966
+ import ora2 from "ora";
967
+ import { confirm as confirm2 } from "@inquirer/prompts";
968
+ async function updateCommand(targetPath) {
969
+ const projectDir = path16.resolve(targetPath || process.cwd());
970
+ const configPath = path16.join(projectDir, AI_KIT_CONFIG_FILE);
971
+ if (!fileExists(configPath)) {
972
+ logError("No ai-kit.config.json found. Run `ai-kit init` first.");
973
+ return;
974
+ }
975
+ const existingConfig = readJsonSafe(configPath);
976
+ if (!existingConfig) {
977
+ logError("Could not read ai-kit.config.json. Run `ai-kit init` to re-initialize.");
978
+ return;
979
+ }
980
+ const existingMajor = parseInt(existingConfig.version.split(".")[0], 10);
981
+ const currentMajor = parseInt(VERSION.split(".")[0], 10);
982
+ if (existingMajor !== currentMajor) {
983
+ logWarning(
984
+ `Config was generated with ai-kit v${existingConfig.version}, but you're running v${VERSION}. Consider running \`ai-kit init\` to re-initialize.`
985
+ );
986
+ }
987
+ const proceed = await confirm2({
988
+ message: "Re-scan project and update all generated files?",
989
+ default: true
990
+ });
991
+ if (!proceed) return;
992
+ const spinner = ora2("Re-scanning project...").start();
993
+ const scan = await scanProject(projectDir);
994
+ spinner.succeed("Project re-scanned");
995
+ logSection("Updating Files");
996
+ const templates = [];
997
+ if (existingConfig.templates.includes("CLAUDE.md") || fileExists(path16.join(projectDir, GENERATED_FILES.claudeMd))) {
998
+ const claudeMdPath = path16.join(projectDir, GENERATED_FILES.claudeMd);
999
+ const newContent = generateClaudeMd(scan);
1000
+ const existing = readFileSafe(claudeMdPath);
1001
+ if (existing) {
1002
+ await fs6.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
1003
+ } else {
1004
+ await fs6.writeFile(claudeMdPath, newContent, "utf-8");
1005
+ }
1006
+ templates.push("CLAUDE.md");
1007
+ logSuccess("CLAUDE.md updated");
1008
+ }
1009
+ if (existingConfig.templates.includes(".cursorrules") || fileExists(path16.join(projectDir, GENERATED_FILES.cursorRules))) {
1010
+ const cursorRulesPath = path16.join(projectDir, GENERATED_FILES.cursorRules);
1011
+ const newContent = generateCursorRules(scan);
1012
+ const existing = readFileSafe(cursorRulesPath);
1013
+ if (existing) {
1014
+ await fs6.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
1015
+ } else {
1016
+ await fs6.writeFile(cursorRulesPath, newContent, "utf-8");
1017
+ }
1018
+ templates.push(".cursorrules");
1019
+ logSuccess(".cursorrules updated");
1020
+ const mdcDir = path16.join(projectDir, GENERATED_FILES.cursorMdcDir);
1021
+ await fs6.ensureDir(mdcDir);
1022
+ const mdcFiles = generateMdcFiles(scan);
1023
+ for (const mdc of mdcFiles) {
1024
+ await fs6.writeFile(path16.join(mdcDir, mdc.filename), mdc.content, "utf-8");
1025
+ }
1026
+ logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
1027
+ }
1028
+ const commands = await copyCommands(projectDir);
1029
+ logSuccess(`${commands.length} slash commands updated`);
1030
+ const guides = await copyGuides(projectDir);
1031
+ logSuccess(`${guides.length} guides updated`);
1032
+ const config = generateConfig(scan, templates, commands, guides);
1033
+ await fs6.writeJson(configPath, config, { spaces: 2 });
1034
+ logSuccess("ai-kit.config.json updated");
1035
+ console.log("");
1036
+ logInfo("All AI configs refreshed with latest project scan.");
1037
+ }
1038
+
1039
+ // src/commands/reset.ts
1040
+ import path17 from "path";
1041
+ import fs7 from "fs-extra";
1042
+ import { confirm as confirm3 } from "@inquirer/prompts";
1043
+ async function resetCommand(targetPath) {
1044
+ const projectDir = path17.resolve(targetPath || process.cwd());
1045
+ logSection("AI Kit \u2014 Reset");
1046
+ logWarning("This will remove all AI Kit generated files:");
1047
+ logInfo(` - ${GENERATED_FILES.claudeMd}`);
1048
+ logInfo(` - ${GENERATED_FILES.cursorRules}`);
1049
+ logInfo(` - ${GENERATED_FILES.cursorMdcDir}/`);
1050
+ logInfo(` - ${GENERATED_FILES.claudeCommands}/`);
1051
+ logInfo(` - ai-kit/`);
1052
+ logInfo(` - ${AI_KIT_CONFIG_FILE}`);
1053
+ console.log("");
1054
+ const proceed = await confirm3({
1055
+ message: "Are you sure? This cannot be undone.",
1056
+ default: false
1057
+ });
1058
+ if (!proceed) {
1059
+ logInfo("Cancelled.");
1060
+ return;
1061
+ }
1062
+ const removed = [];
1063
+ const claudeMdPath = path17.join(projectDir, GENERATED_FILES.claudeMd);
1064
+ if (fileExists(claudeMdPath)) {
1065
+ await fs7.remove(claudeMdPath);
1066
+ removed.push(GENERATED_FILES.claudeMd);
1067
+ }
1068
+ const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
1069
+ if (fileExists(cursorPath)) {
1070
+ await fs7.remove(cursorPath);
1071
+ removed.push(GENERATED_FILES.cursorRules);
1072
+ }
1073
+ const cursorMdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
1074
+ if (fileExists(cursorMdcDir)) {
1075
+ await fs7.remove(cursorMdcDir);
1076
+ removed.push(GENERATED_FILES.cursorMdcDir);
1077
+ }
1078
+ const commandsDir = path17.join(projectDir, GENERATED_FILES.claudeCommands);
1079
+ if (fileExists(commandsDir)) {
1080
+ await fs7.remove(commandsDir);
1081
+ removed.push(GENERATED_FILES.claudeCommands);
1082
+ }
1083
+ const aiKitDir = path17.join(projectDir, "ai-kit");
1084
+ if (fileExists(aiKitDir)) {
1085
+ await fs7.remove(aiKitDir);
1086
+ removed.push("ai-kit/");
1087
+ }
1088
+ const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
1089
+ if (fileExists(configPath)) {
1090
+ await fs7.remove(configPath);
1091
+ removed.push(AI_KIT_CONFIG_FILE);
1092
+ }
1093
+ logSection("Reset Complete");
1094
+ if (removed.length > 0) {
1095
+ removed.forEach((f) => logSuccess(`Removed ${f}`));
1096
+ } else {
1097
+ logInfo("No AI Kit files found to remove.");
1098
+ }
1099
+ }
1100
+
1101
+ // src/commands/tokens.ts
1102
+ import path18 from "path";
1103
+ import fs8 from "fs-extra";
1104
+ import chalk2 from "chalk";
1105
+ import ora3 from "ora";
1106
+ import os from "os";
1107
+ var PRICING = {
1108
+ sonnet: { input: 3, output: 15, cacheRead: 0.3 },
1109
+ opus: { input: 15, output: 75, cacheRead: 0.3 }
1110
+ };
1111
+ var PLAN_BUDGET = 20;
1112
+ function findSessionFiles() {
1113
+ const claudeDir = path18.join(os.homedir(), ".claude", "projects");
1114
+ if (!fs8.existsSync(claudeDir)) return [];
1115
+ const files = [];
1116
+ function walkDir(dir) {
1117
+ try {
1118
+ const entries = fs8.readdirSync(dir, { withFileTypes: true });
1119
+ for (const entry of entries) {
1120
+ const full = path18.join(dir, entry.name);
1121
+ if (entry.isDirectory()) {
1122
+ walkDir(full);
1123
+ } else if (entry.name.endsWith(".jsonl")) {
1124
+ files.push(full);
1125
+ }
1126
+ }
1127
+ } catch {
1128
+ }
1129
+ }
1130
+ walkDir(claudeDir);
1131
+ return files;
1132
+ }
1133
+ function parseSessionFile(filePath) {
1134
+ try {
1135
+ const content = fs8.readFileSync(filePath, "utf-8");
1136
+ const lines = content.split("\n").filter((l) => l.trim());
1137
+ const usage = {
1138
+ inputTokens: 0,
1139
+ outputTokens: 0,
1140
+ cacheReadTokens: 0,
1141
+ cacheCreationTokens: 0
1142
+ };
1143
+ let model = "sonnet";
1144
+ let messageCount = 0;
1145
+ let sessionDate = "";
1146
+ for (const line of lines) {
1147
+ try {
1148
+ const obj = JSON.parse(line);
1149
+ if (obj.timestamp && !sessionDate) {
1150
+ sessionDate = new Date(obj.timestamp).toISOString().slice(0, 10);
1151
+ }
1152
+ if (obj.model) {
1153
+ const m = String(obj.model).toLowerCase();
1154
+ if (m.includes("opus")) model = "opus";
1155
+ else model = "sonnet";
1156
+ }
1157
+ if (obj.type === "assistant" || obj.role === "assistant") {
1158
+ messageCount++;
1159
+ }
1160
+ const u = obj.usage || obj.message?.usage || obj.costInfo || obj.result?.usage || null;
1161
+ if (u) {
1162
+ usage.inputTokens += u.input_tokens || u.inputTokens || 0;
1163
+ usage.outputTokens += u.output_tokens || u.outputTokens || 0;
1164
+ usage.cacheReadTokens += u.cache_read_input_tokens || u.cacheReadTokens || u.cache_read || 0;
1165
+ usage.cacheCreationTokens += u.cache_creation_input_tokens || u.cacheCreationTokens || u.cache_creation || 0;
1166
+ }
1167
+ } catch {
1168
+ }
1169
+ }
1170
+ if (usage.inputTokens === 0 && usage.outputTokens === 0) return null;
1171
+ if (!sessionDate) {
1172
+ const stat = fs8.statSync(filePath);
1173
+ sessionDate = stat.mtime.toISOString().slice(0, 10);
1174
+ }
1175
+ const sessionId = path18.basename(filePath, ".jsonl");
1176
+ return {
1177
+ sessionId,
1178
+ filePath,
1179
+ date: sessionDate,
1180
+ usage,
1181
+ model,
1182
+ messageCount
1183
+ };
1184
+ } catch {
1185
+ return null;
1186
+ }
1187
+ }
1188
+ function calculateCost(usage, model = "sonnet") {
1189
+ const rates = model === "opus" ? PRICING.opus : PRICING.sonnet;
1190
+ const inputCost = usage.inputTokens / 1e6 * rates.input;
1191
+ const outputCost = usage.outputTokens / 1e6 * rates.output;
1192
+ const cacheCost = usage.cacheReadTokens / 1e6 * rates.cacheRead;
1193
+ return inputCost + outputCost + cacheCost;
1194
+ }
1195
+ function aggregateByDate(sessions) {
1196
+ const map = /* @__PURE__ */ new Map();
1197
+ for (const s of sessions) {
1198
+ const existing = map.get(s.date) || {
1199
+ sessions: 0,
1200
+ usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 },
1201
+ models: []
1202
+ };
1203
+ existing.sessions++;
1204
+ existing.usage.inputTokens += s.usage.inputTokens;
1205
+ existing.usage.outputTokens += s.usage.outputTokens;
1206
+ existing.usage.cacheReadTokens += s.usage.cacheReadTokens;
1207
+ existing.usage.cacheCreationTokens += s.usage.cacheCreationTokens;
1208
+ existing.models.push(s.model);
1209
+ map.set(s.date, existing);
1210
+ }
1211
+ return Array.from(map.entries()).map(([date, data]) => ({
1212
+ date,
1213
+ sessions: data.sessions,
1214
+ usage: data.usage,
1215
+ cost: calculateCost(data.usage, data.models.includes("opus") ? "opus" : "sonnet")
1216
+ })).sort((a, b) => b.date.localeCompare(a.date));
1217
+ }
1218
+ function filterDateRange(sessions, startDate, endDate) {
1219
+ return sessions.filter((s) => s.date >= startDate && s.date <= endDate);
1220
+ }
1221
+ function sumUsage(sessions) {
1222
+ return sessions.reduce(
1223
+ (acc, s) => ({
1224
+ inputTokens: acc.inputTokens + s.usage.inputTokens,
1225
+ outputTokens: acc.outputTokens + s.usage.outputTokens,
1226
+ cacheReadTokens: acc.cacheReadTokens + s.usage.cacheReadTokens,
1227
+ cacheCreationTokens: acc.cacheCreationTokens + s.usage.cacheCreationTokens
1228
+ }),
1229
+ { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 }
1230
+ );
1231
+ }
1232
+ function sumCost(sessions) {
1233
+ return sessions.reduce((acc, s) => acc + calculateCost(s.usage, s.model), 0);
1234
+ }
1235
+ function fmt(n) {
1236
+ return n.toLocaleString("en-US");
1237
+ }
1238
+ function fmtCost(n) {
1239
+ return `$${n.toFixed(2)}`;
1240
+ }
1241
+ function progressBar(percent, width = 20) {
1242
+ const filled = Math.round(percent / 100 * width);
1243
+ const empty = width - filled;
1244
+ const bar = chalk2.green("\u2588".repeat(filled)) + chalk2.gray("\u2591".repeat(empty));
1245
+ return bar;
1246
+ }
1247
+ function printPeriodSummary(label, sessions) {
1248
+ const usage = sumUsage(sessions);
1249
+ const cost = sumCost(sessions);
1250
+ console.log(`
1251
+ ${chalk2.bold(label)}`);
1252
+ console.log(` Sessions: ${chalk2.cyan(String(sessions.length))}`);
1253
+ console.log(` Input tokens: ${chalk2.white(fmt(usage.inputTokens))}`);
1254
+ console.log(` Output tokens: ${chalk2.white(fmt(usage.outputTokens))}`);
1255
+ console.log(` Cache read: ${chalk2.white(fmt(usage.cacheReadTokens))}`);
1256
+ console.log(` Estimated cost: ${chalk2.yellow("~" + fmtCost(cost))}`);
1257
+ }
1258
+ async function tokensCommand(options = {}) {
1259
+ logSection("AI Kit \u2014 Token Usage");
1260
+ const spinner = ora3("Scanning Claude Code session logs...").start();
1261
+ const sessionFiles = findSessionFiles();
1262
+ if (sessionFiles.length === 0) {
1263
+ spinner.fail("No Claude Code session logs found");
1264
+ logInfo("Session logs are stored in ~/.claude/projects/");
1265
+ logInfo("Use Claude Code to generate some activity first.");
1266
+ return;
1267
+ }
1268
+ const sessions = [];
1269
+ for (const file of sessionFiles) {
1270
+ const s = parseSessionFile(file);
1271
+ if (s) sessions.push(s);
1272
+ }
1273
+ spinner.succeed(`Parsed ${sessions.length} sessions from ${sessionFiles.length} log files`);
1274
+ if (sessions.length === 0) {
1275
+ logWarning("No token usage data found in session logs.");
1276
+ return;
1277
+ }
1278
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1279
+ const dayOfWeek = (/* @__PURE__ */ new Date()).getDay();
1280
+ const mondayOffset = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
1281
+ const weekStart = /* @__PURE__ */ new Date();
1282
+ weekStart.setDate(weekStart.getDate() - mondayOffset);
1283
+ const weekStartStr = weekStart.toISOString().slice(0, 10);
1284
+ const monthStart = today.slice(0, 7) + "-01";
1285
+ const todaySessions = filterDateRange(sessions, today, today);
1286
+ const weekSessions = filterDateRange(sessions, weekStartStr, today);
1287
+ const monthSessions = filterDateRange(sessions, monthStart, today);
1288
+ printPeriodSummary(`Today (${today})`, todaySessions);
1289
+ printPeriodSummary("This Week", weekSessions);
1290
+ printPeriodSummary("This Month", monthSessions);
1291
+ const monthCost = sumCost(monthSessions);
1292
+ const budgetPercent = Math.min(monthCost / PLAN_BUDGET * 100, 100);
1293
+ const daysInMonth = new Date(
1294
+ (/* @__PURE__ */ new Date()).getFullYear(),
1295
+ (/* @__PURE__ */ new Date()).getMonth() + 1,
1296
+ 0
1297
+ ).getDate();
1298
+ const dayOfMonth = (/* @__PURE__ */ new Date()).getDate();
1299
+ const daysRemaining = daysInMonth - dayOfMonth;
1300
+ const dailyAvg = dayOfMonth > 0 ? monthCost / dayOfMonth : 0;
1301
+ const estimatedDaysLeft = dailyAvg > 0 ? Math.floor((PLAN_BUDGET - monthCost) / dailyAvg) : daysRemaining;
1302
+ console.log(`
1303
+ ${chalk2.bold(`$${PLAN_BUDGET} Plan Budget`)}`);
1304
+ console.log(
1305
+ ` ${progressBar(budgetPercent)} ${Math.round(budgetPercent)}% used (~${fmtCost(monthCost)} of ${fmtCost(PLAN_BUDGET)})`
1306
+ );
1307
+ console.log(
1308
+ ` Estimated days remaining at this rate: ${chalk2.cyan(String(Math.max(estimatedDaysLeft, 0)))} days`
1309
+ );
1310
+ console.log(` Daily average: ${chalk2.yellow(fmtCost(dailyAvg))}`);
1311
+ console.log(
1312
+ `
1313
+ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
1314
+ );
1315
+ console.log(
1316
+ chalk2.dim(" it's cheaper than a failed implementation attempt.")
1317
+ );
1318
+ console.log("");
1319
+ if (options.export) {
1320
+ await exportDashboard(sessions, todaySessions, weekSessions, monthSessions);
1321
+ }
1322
+ }
1323
+ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSessions) {
1324
+ const spinner = ora3("Generating dashboard...").start();
1325
+ const exportData = {
1326
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1327
+ planBudget: PLAN_BUDGET,
1328
+ daily: aggregateByDate(allSessions),
1329
+ sessions: allSessions.sort((a, b) => b.date.localeCompare(a.date)).slice(0, 50).map((s) => ({
1330
+ ...s,
1331
+ filePath: s.filePath
1332
+ // keep for reference
1333
+ })),
1334
+ totals: {
1335
+ today: {
1336
+ usage: sumUsage(todaySessions),
1337
+ cost: sumCost(todaySessions),
1338
+ sessions: todaySessions.length
1339
+ },
1340
+ thisWeek: {
1341
+ usage: sumUsage(weekSessions),
1342
+ cost: sumCost(weekSessions),
1343
+ sessions: weekSessions.length
1344
+ },
1345
+ thisMonth: {
1346
+ usage: sumUsage(monthSessions),
1347
+ cost: sumCost(monthSessions),
1348
+ sessions: monthSessions.length
1349
+ }
1350
+ }
1351
+ };
1352
+ const outputDir = process.cwd();
1353
+ const dataPath = path18.join(outputDir, "token-data.json");
1354
+ const dashboardSrc = path18.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
1355
+ const dashboardDest = path18.join(outputDir, "token-dashboard.html");
1356
+ await fs8.writeJson(dataPath, exportData, { spaces: 2 });
1357
+ logInfo(`Token data written to ${dataPath}`);
1358
+ if (await fs8.pathExists(dashboardSrc)) {
1359
+ await fs8.copy(dashboardSrc, dashboardDest, { overwrite: true });
1360
+ logInfo(`Dashboard copied to ${dashboardDest}`);
1361
+ } else {
1362
+ logWarning("Dashboard template not found. Skipping HTML export.");
1363
+ }
1364
+ spinner.succeed("Dashboard exported");
1365
+ try {
1366
+ const { exec } = await import("child_process");
1367
+ const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1368
+ exec(`${openCmd} "${dashboardDest}"`);
1369
+ logInfo("Opening dashboard in browser...");
1370
+ } catch {
1371
+ logInfo(`Open ${dashboardDest} in your browser to view the dashboard.`);
1372
+ }
1373
+ }
1374
+
1375
+ // src/index.ts
1376
+ var program = new Command();
1377
+ program.name("ai-kit").description(
1378
+ "AI-powered project setup \u2014 generates CLAUDE.md, .cursorrules, slash commands, and guides tailored to your stack."
1379
+ ).version(VERSION);
1380
+ program.command("init").description("Scan your project and generate AI configs").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
1381
+ try {
1382
+ await initCommand(targetPath);
1383
+ } catch (err) {
1384
+ if (err.name === "ExitPromptError") {
1385
+ process.exit(0);
1386
+ }
1387
+ console.error(err);
1388
+ process.exit(1);
1389
+ }
1390
+ });
1391
+ program.command("update").description("Re-scan and update all generated AI configs").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
1392
+ try {
1393
+ await updateCommand(targetPath);
1394
+ } catch (err) {
1395
+ if (err.name === "ExitPromptError") {
1396
+ process.exit(0);
1397
+ }
1398
+ console.error(err);
1399
+ process.exit(1);
1400
+ }
1401
+ });
1402
+ program.command("reset").description("Remove all AI Kit generated files").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
1403
+ try {
1404
+ await resetCommand(targetPath);
1405
+ } catch (err) {
1406
+ if (err.name === "ExitPromptError") {
1407
+ process.exit(0);
1408
+ }
1409
+ console.error(err);
1410
+ process.exit(1);
1411
+ }
1412
+ });
1413
+ program.command("tokens").description("Show token usage summary and cost estimates").option("--export", "Export data and open HTML dashboard").action(async (opts) => {
1414
+ try {
1415
+ await tokensCommand(opts);
1416
+ } catch (err) {
1417
+ if (err.name === "ExitPromptError") {
1418
+ process.exit(0);
1419
+ }
1420
+ console.error(err);
1421
+ process.exit(1);
1422
+ }
1423
+ });
1424
+ program.parse();
1425
+ //# sourceMappingURL=index.js.map