@mikulgohil/ai-kit 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/commands/bundle-check.md +118 -0
- package/commands/changelog.md +103 -0
- package/commands/ci-fix.md +102 -0
- package/commands/db-migrate.md +138 -0
- package/commands/dependency-graph.md +138 -0
- package/commands/docker-debug.md +111 -0
- package/commands/i18n-check.md +138 -0
- package/commands/perf-audit.md +131 -0
- package/commands/release.md +90 -0
- package/commands/schema-gen.md +132 -0
- package/commands/storybook-gen.md +91 -0
- package/commands/visual-diff.md +131 -0
- package/dist/index.js +1100 -83
- package/dist/index.js.map +1 -1
- package/guides/getting-started.md +2 -2
- package/package.json +8 -2
- package/templates/claude-md/base.md +4 -2
package/dist/index.js
CHANGED
|
@@ -13,19 +13,31 @@ var TEMPLATES_DIR = path.join(PACKAGE_ROOT, "templates");
|
|
|
13
13
|
var COMMANDS_DIR = path.join(PACKAGE_ROOT, "commands");
|
|
14
14
|
var GUIDES_DIR = path.join(PACKAGE_ROOT, "guides");
|
|
15
15
|
var DOCS_SCAFFOLDS_DIR = path.join(PACKAGE_ROOT, "docs-scaffolds");
|
|
16
|
-
var VERSION = "1.
|
|
16
|
+
var VERSION = "1.1.0";
|
|
17
17
|
var AI_KIT_CONFIG_FILE = "ai-kit.config.json";
|
|
18
18
|
var GENERATED_FILES = {
|
|
19
19
|
claudeMd: "CLAUDE.md",
|
|
20
20
|
cursorRules: ".cursorrules",
|
|
21
21
|
cursorMdcDir: ".cursor/rules",
|
|
22
22
|
claudeSettings: ".claude/settings.json",
|
|
23
|
-
claudeCommands: ".claude/commands"
|
|
23
|
+
claudeCommands: ".claude/commands",
|
|
24
|
+
claudeSkills: ".claude/skills",
|
|
25
|
+
cursorSkills: ".cursor/skills"
|
|
24
26
|
};
|
|
27
|
+
var TEMPLATE_FRAGMENTS = [
|
|
28
|
+
"base",
|
|
29
|
+
"nextjs-app-router",
|
|
30
|
+
"nextjs-pages-router",
|
|
31
|
+
"sitecore-xmc",
|
|
32
|
+
"tailwind",
|
|
33
|
+
"typescript",
|
|
34
|
+
"monorepo",
|
|
35
|
+
"figma"
|
|
36
|
+
];
|
|
25
37
|
|
|
26
38
|
// src/commands/init.ts
|
|
27
39
|
import path15 from "path";
|
|
28
|
-
import
|
|
40
|
+
import fs6 from "fs-extra";
|
|
29
41
|
import { select, confirm } from "@inquirer/prompts";
|
|
30
42
|
import ora from "ora";
|
|
31
43
|
|
|
@@ -388,8 +400,7 @@ function detectMcpServers(projectPath) {
|
|
|
388
400
|
figma: combined.includes("figma"),
|
|
389
401
|
github: combined.includes("github"),
|
|
390
402
|
context7: combined.includes("context7"),
|
|
391
|
-
perplexity: combined.includes("perplexity")
|
|
392
|
-
notion: combined.includes("notion")
|
|
403
|
+
perplexity: combined.includes("perplexity")
|
|
393
404
|
};
|
|
394
405
|
}
|
|
395
406
|
|
|
@@ -430,6 +441,7 @@ async function scanProject(projectPath) {
|
|
|
430
441
|
|
|
431
442
|
// src/generator/assembler.ts
|
|
432
443
|
import path11 from "path";
|
|
444
|
+
import fs2 from "fs-extra";
|
|
433
445
|
function readTemplate(relativePath) {
|
|
434
446
|
const fullPath = path11.join(TEMPLATES_DIR, relativePath);
|
|
435
447
|
const content = readFileSafe(fullPath);
|
|
@@ -438,21 +450,51 @@ function readTemplate(relativePath) {
|
|
|
438
450
|
}
|
|
439
451
|
return content.trim();
|
|
440
452
|
}
|
|
441
|
-
function
|
|
453
|
+
function loadCustomFragments(projectPath) {
|
|
454
|
+
const customDir = path11.join(projectPath, ".ai-kit", "fragments");
|
|
455
|
+
if (!fs2.existsSync(customDir)) return [];
|
|
456
|
+
try {
|
|
457
|
+
const files = fs2.readdirSync(customDir).filter((f) => f.endsWith(".md"));
|
|
458
|
+
return files.map((f) => {
|
|
459
|
+
const content = fs2.readFileSync(path11.join(customDir, f), "utf-8");
|
|
460
|
+
return content.trim();
|
|
461
|
+
}).filter(Boolean);
|
|
462
|
+
} catch {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function assembleTemplate(subfolder, fragments, variables, options) {
|
|
442
467
|
const versionComment = `<!-- Generated by ai-kit v${VERSION} -->`;
|
|
443
468
|
const header = readTemplate("header.md");
|
|
444
469
|
const body = fragments.map((name) => readTemplate(`${subfolder}/${name}.md`)).join("\n\n---\n\n");
|
|
470
|
+
const strictnessNote = options?.strictness && options.strictness !== "standard" ? `
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
> **Strictness Level: ${options.strictness}**
|
|
475
|
+
> ${getStrictnessDescription(options.strictness)}` : "";
|
|
476
|
+
const customSection = options?.customFragments?.length ? "\n\n---\n\n" + options.customFragments.join("\n\n---\n\n") : "";
|
|
445
477
|
const full = `${versionComment}
|
|
446
478
|
${header}
|
|
447
479
|
|
|
448
480
|
---
|
|
449
481
|
|
|
450
|
-
${body}`;
|
|
482
|
+
${body}${strictnessNote}${customSection}`;
|
|
451
483
|
const replaced = replacePlaceholders(full, variables);
|
|
452
484
|
return `<!-- AI-KIT:START -->
|
|
453
485
|
${replaced}
|
|
454
486
|
<!-- AI-KIT:END -->`;
|
|
455
487
|
}
|
|
488
|
+
function getStrictnessDescription(level) {
|
|
489
|
+
switch (level) {
|
|
490
|
+
case "strict":
|
|
491
|
+
return "Enforce all rules strictly. No exceptions without explicit approval. All PRs must pass every check. Zero tolerance for `any` types, missing tests, or accessibility gaps.";
|
|
492
|
+
case "relaxed":
|
|
493
|
+
return "Rules are guidelines, not hard requirements. Prioritize shipping speed over perfection. Skip non-critical checks when under time pressure. Use best judgment.";
|
|
494
|
+
default:
|
|
495
|
+
return "Standard enforcement. Follow rules by default, use judgment for edge cases.";
|
|
496
|
+
}
|
|
497
|
+
}
|
|
456
498
|
function replacePlaceholders(content, variables) {
|
|
457
499
|
let result = content;
|
|
458
500
|
for (const [key, value] of Object.entries(variables)) {
|
|
@@ -489,10 +531,13 @@ function selectFragments(scan) {
|
|
|
489
531
|
}
|
|
490
532
|
return fragments;
|
|
491
533
|
}
|
|
492
|
-
function generateClaudeMd(scan) {
|
|
534
|
+
function generateClaudeMd(scan, options) {
|
|
493
535
|
const fragments = selectFragments(scan);
|
|
494
536
|
const variables = buildVariables(scan);
|
|
495
|
-
return assembleTemplate("claude-md", fragments, variables
|
|
537
|
+
return assembleTemplate("claude-md", fragments, variables, {
|
|
538
|
+
customFragments: options?.customFragments,
|
|
539
|
+
strictness: options?.strictness
|
|
540
|
+
});
|
|
496
541
|
}
|
|
497
542
|
function buildVariables(scan) {
|
|
498
543
|
const techStack = [];
|
|
@@ -526,10 +571,13 @@ function buildVariables(scan) {
|
|
|
526
571
|
}
|
|
527
572
|
|
|
528
573
|
// src/generator/cursorrules.ts
|
|
529
|
-
function generateCursorRules(scan) {
|
|
574
|
+
function generateCursorRules(scan, options) {
|
|
530
575
|
const fragments = selectFragments(scan);
|
|
531
576
|
const variables = buildCursorVariables(scan);
|
|
532
|
-
return assembleTemplate("cursorrules", fragments, variables
|
|
577
|
+
return assembleTemplate("cursorrules", fragments, variables, {
|
|
578
|
+
customFragments: options?.customFragments,
|
|
579
|
+
strictness: options?.strictness
|
|
580
|
+
});
|
|
533
581
|
}
|
|
534
582
|
function buildCursorVariables(scan) {
|
|
535
583
|
const techStack = [];
|
|
@@ -619,21 +667,23 @@ ${replaced}`
|
|
|
619
667
|
}
|
|
620
668
|
|
|
621
669
|
// src/generator/config.ts
|
|
622
|
-
function generateConfig(scan, templates, commands, guides) {
|
|
670
|
+
function generateConfig(scan, templates, commands, guides, options) {
|
|
623
671
|
return {
|
|
624
672
|
version: VERSION,
|
|
625
673
|
scanResult: scan,
|
|
626
674
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
627
675
|
templates,
|
|
628
676
|
commands,
|
|
629
|
-
guides
|
|
677
|
+
guides,
|
|
678
|
+
strictness: options?.strictness || "standard",
|
|
679
|
+
customFragments: options?.customFragments || []
|
|
630
680
|
};
|
|
631
681
|
}
|
|
632
682
|
|
|
633
|
-
// src/copier/
|
|
683
|
+
// src/copier/skills.ts
|
|
634
684
|
import path12 from "path";
|
|
635
|
-
import
|
|
636
|
-
var
|
|
685
|
+
import fs3 from "fs-extra";
|
|
686
|
+
var AVAILABLE_SKILLS = [
|
|
637
687
|
"prompt-help",
|
|
638
688
|
"review",
|
|
639
689
|
"fix-bug",
|
|
@@ -659,26 +709,94 @@ var AVAILABLE_COMMANDS = [
|
|
|
659
709
|
"sitecore-debug",
|
|
660
710
|
"responsive-check",
|
|
661
711
|
"document",
|
|
662
|
-
"token-tips"
|
|
712
|
+
"token-tips",
|
|
713
|
+
// New skills (v1.1.0)
|
|
714
|
+
"perf-audit",
|
|
715
|
+
"bundle-check",
|
|
716
|
+
"i18n-check",
|
|
717
|
+
"schema-gen",
|
|
718
|
+
"docker-debug",
|
|
719
|
+
"ci-fix",
|
|
720
|
+
"changelog",
|
|
721
|
+
"release",
|
|
722
|
+
"storybook-gen",
|
|
723
|
+
"visual-diff",
|
|
724
|
+
"db-migrate",
|
|
725
|
+
"dependency-graph"
|
|
663
726
|
];
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
727
|
+
var SKILL_DESCRIPTIONS = {
|
|
728
|
+
"prompt-help": "Help developers write effective AI prompts with structured context",
|
|
729
|
+
"review": "Deep code review following project coding standards",
|
|
730
|
+
"fix-bug": "Systematic debugging workflow with root cause analysis and regression testing",
|
|
731
|
+
"new-component": "Scaffold new React components with types, tests, and documentation",
|
|
732
|
+
"new-page": "Scaffold new Next.js pages/routes with proper file structure",
|
|
733
|
+
"understand": "Explain code architecture, data flow, and design decisions",
|
|
734
|
+
"test": "Generate unit and integration tests with React Testing Library",
|
|
735
|
+
"optimize": "Analyze and fix performance issues in components and pages",
|
|
736
|
+
"figma-to-code": "Implement Figma designs using project design tokens",
|
|
737
|
+
"design-tokens": "Audit and manage design token systems",
|
|
738
|
+
"accessibility-audit": "WCAG 2.1 AA accessibility compliance audit",
|
|
739
|
+
"security-check": "Scan for XSS, injection, secrets, and OWASP Top 10 vulnerabilities",
|
|
740
|
+
"refactor": "Restructure code to improve readability without changing behavior",
|
|
741
|
+
"api-route": "Scaffold Next.js API routes with validation, typing, and error handling",
|
|
742
|
+
"pre-pr": "Pre-pull-request checklist covering types, a11y, security, tests, and more",
|
|
743
|
+
"migrate": "Guide framework and library migrations step by step",
|
|
744
|
+
"error-boundary": "Generate error boundaries, loading states, and fallback UI",
|
|
745
|
+
"type-fix": "Fix TypeScript issues \u2014 replace any types, add null checks, tighten types",
|
|
746
|
+
"extract-hook": "Extract component logic into reusable custom React hooks",
|
|
747
|
+
"dep-check": "Audit dependencies for unused, outdated, vulnerable, and bloated packages",
|
|
748
|
+
"env-setup": "Generate .env.example, validate environment variables, check for leaked secrets",
|
|
749
|
+
"commit-msg": "Generate conventional commit messages from staged git changes",
|
|
750
|
+
"sitecore-debug": "Debug Sitecore XM Cloud integration issues",
|
|
751
|
+
"responsive-check": "Audit responsive design \u2014 breakpoints, touch targets, overflow",
|
|
752
|
+
"document": "Generate documentation for existing components and utilities",
|
|
753
|
+
"token-tips": "Token usage optimization strategies for AI coding assistants",
|
|
754
|
+
// New skills (v1.1.0)
|
|
755
|
+
"perf-audit": "Lighthouse-style performance audit covering Core Web Vitals, resource loading, and caching",
|
|
756
|
+
"bundle-check": "Analyze bundle size, find heavy imports, suggest tree-shaking and code splitting",
|
|
757
|
+
"i18n-check": "Find hardcoded strings, missing translation keys, and internationalization gaps",
|
|
758
|
+
"schema-gen": "Generate TypeScript types and Zod schemas from API responses, JSON, or GraphQL",
|
|
759
|
+
"docker-debug": "Analyze Dockerfiles for security, efficiency, and best practice violations",
|
|
760
|
+
"ci-fix": "Debug CI/CD pipeline failures, optimize caching, and improve build performance",
|
|
761
|
+
"changelog": "Generate formatted changelogs from git history following Keep a Changelog format",
|
|
762
|
+
"release": "Guided release workflow with versioning, changelog, tagging, and release notes",
|
|
763
|
+
"storybook-gen": "Generate Storybook stories with controls, play functions, and visual tests",
|
|
764
|
+
"visual-diff": "Visual regression testing plan for changed components and pages",
|
|
765
|
+
"db-migrate": "Create safe database migrations with rollback strategies for Prisma, Drizzle, or SQL",
|
|
766
|
+
"dependency-graph": "Map module dependencies, find circular imports, and analyze coupling metrics"
|
|
767
|
+
};
|
|
768
|
+
async function copySkills(targetDir) {
|
|
667
769
|
const copied = [];
|
|
668
|
-
for (const
|
|
669
|
-
const src = path12.join(COMMANDS_DIR, `${
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
770
|
+
for (const skill of AVAILABLE_SKILLS) {
|
|
771
|
+
const src = path12.join(COMMANDS_DIR, `${skill}.md`);
|
|
772
|
+
if (!await fs3.pathExists(src)) continue;
|
|
773
|
+
const content = await fs3.readFile(src, "utf-8");
|
|
774
|
+
const description = SKILL_DESCRIPTIONS[skill] || skill;
|
|
775
|
+
const claudeSkillDir = path12.join(targetDir, ".claude", "skills", skill);
|
|
776
|
+
await fs3.ensureDir(claudeSkillDir);
|
|
777
|
+
await fs3.writeFile(
|
|
778
|
+
path12.join(claudeSkillDir, "SKILL.md"),
|
|
779
|
+
content,
|
|
780
|
+
"utf-8"
|
|
781
|
+
);
|
|
782
|
+
const cursorSkillDir = path12.join(targetDir, ".cursor", "skills", skill);
|
|
783
|
+
await fs3.ensureDir(cursorSkillDir);
|
|
784
|
+
await fs3.writeFile(
|
|
785
|
+
path12.join(cursorSkillDir, "SKILL.md"),
|
|
786
|
+
content,
|
|
787
|
+
"utf-8"
|
|
788
|
+
);
|
|
789
|
+
const legacyDir = path12.join(targetDir, ".claude", "commands");
|
|
790
|
+
await fs3.ensureDir(legacyDir);
|
|
791
|
+
await fs3.copy(src, path12.join(legacyDir, `${skill}.md`), { overwrite: true });
|
|
792
|
+
copied.push(skill);
|
|
675
793
|
}
|
|
676
794
|
return copied;
|
|
677
795
|
}
|
|
678
796
|
|
|
679
797
|
// src/copier/guides.ts
|
|
680
798
|
import path13 from "path";
|
|
681
|
-
import
|
|
799
|
+
import fs4 from "fs-extra";
|
|
682
800
|
var AVAILABLE_GUIDES = [
|
|
683
801
|
"getting-started",
|
|
684
802
|
"prompt-playbook",
|
|
@@ -688,13 +806,13 @@ var AVAILABLE_GUIDES = [
|
|
|
688
806
|
];
|
|
689
807
|
async function copyGuides(targetDir) {
|
|
690
808
|
const guidesTarget = path13.join(targetDir, "ai-kit", "guides");
|
|
691
|
-
await
|
|
809
|
+
await fs4.ensureDir(guidesTarget);
|
|
692
810
|
const copied = [];
|
|
693
811
|
for (const guide of AVAILABLE_GUIDES) {
|
|
694
812
|
const src = path13.join(GUIDES_DIR, `${guide}.md`);
|
|
695
813
|
const dest = path13.join(guidesTarget, `${guide}.md`);
|
|
696
|
-
if (await
|
|
697
|
-
await
|
|
814
|
+
if (await fs4.pathExists(src)) {
|
|
815
|
+
await fs4.copy(src, dest, { overwrite: true });
|
|
698
816
|
copied.push(guide);
|
|
699
817
|
}
|
|
700
818
|
}
|
|
@@ -703,20 +821,20 @@ async function copyGuides(targetDir) {
|
|
|
703
821
|
|
|
704
822
|
// src/copier/docs.ts
|
|
705
823
|
import path14 from "path";
|
|
706
|
-
import
|
|
824
|
+
import fs5 from "fs-extra";
|
|
707
825
|
var DOC_SCAFFOLDS = ["mistakes-log", "decisions-log", "time-log"];
|
|
708
826
|
async function scaffoldDocs(targetDir) {
|
|
709
827
|
const docsTarget = path14.join(targetDir, "docs");
|
|
710
|
-
await
|
|
828
|
+
await fs5.ensureDir(docsTarget);
|
|
711
829
|
const created = [];
|
|
712
830
|
for (const doc of DOC_SCAFFOLDS) {
|
|
713
831
|
const src = path14.join(DOCS_SCAFFOLDS_DIR, `${doc}.md`);
|
|
714
832
|
const dest = path14.join(docsTarget, `${doc}.md`);
|
|
715
|
-
if (await
|
|
833
|
+
if (await fs5.pathExists(dest)) {
|
|
716
834
|
continue;
|
|
717
835
|
}
|
|
718
|
-
if (await
|
|
719
|
-
await
|
|
836
|
+
if (await fs5.pathExists(src)) {
|
|
837
|
+
await fs5.copy(src, dest);
|
|
720
838
|
created.push(doc);
|
|
721
839
|
}
|
|
722
840
|
}
|
|
@@ -759,16 +877,18 @@ async function initCommand(targetPath) {
|
|
|
759
877
|
const clarifications = await askClarifications(scan);
|
|
760
878
|
scan = applyClarifications(scan, clarifications);
|
|
761
879
|
const tools = await selectTools();
|
|
880
|
+
const strictness = await selectStrictness();
|
|
881
|
+
const customFragments = loadCustomFragments(projectDir);
|
|
762
882
|
const conflict = await selectConflictStrategy(projectDir);
|
|
763
883
|
logSection("Generating Files");
|
|
764
|
-
const results = await generate(projectDir, scan, tools, conflict);
|
|
884
|
+
const results = await generate(projectDir, scan, tools, conflict, { strictness, customFragments });
|
|
765
885
|
logSection("Setup Complete");
|
|
766
886
|
if (results.claudeMd) logSuccess(`CLAUDE.md generated`);
|
|
767
887
|
if (results.cursorRules) logSuccess(`.cursorrules generated`);
|
|
768
888
|
if (results.cursorMdcFiles > 0)
|
|
769
889
|
logSuccess(`${results.cursorMdcFiles} .cursor/rules/*.mdc files generated`);
|
|
770
890
|
if (results.commands.length > 0)
|
|
771
|
-
logSuccess(`${results.commands.length}
|
|
891
|
+
logSuccess(`${results.commands.length} skills generated (.claude/skills/ + .cursor/skills/)`);
|
|
772
892
|
if (results.guides.length > 0)
|
|
773
893
|
logSuccess(`${results.guides.length} guides added to ai-kit/guides/`);
|
|
774
894
|
if (results.docs.length > 0)
|
|
@@ -822,6 +942,17 @@ async function selectTools() {
|
|
|
822
942
|
cursor: tool === "both" || tool === "cursor"
|
|
823
943
|
};
|
|
824
944
|
}
|
|
945
|
+
async function selectStrictness() {
|
|
946
|
+
return select({
|
|
947
|
+
message: "How strictly should AI enforce these rules?",
|
|
948
|
+
choices: [
|
|
949
|
+
{ name: "Standard \u2014 follow rules by default, use judgment for edge cases", value: "standard" },
|
|
950
|
+
{ name: "Strict \u2014 enforce all rules, no exceptions without approval", value: "strict" },
|
|
951
|
+
{ name: "Relaxed \u2014 rules are guidelines, prioritize shipping speed", value: "relaxed" }
|
|
952
|
+
],
|
|
953
|
+
default: "standard"
|
|
954
|
+
});
|
|
955
|
+
}
|
|
825
956
|
async function selectConflictStrategy(projectDir) {
|
|
826
957
|
const hasExisting = fileExists(path15.join(projectDir, GENERATED_FILES.claudeMd)) || fileExists(path15.join(projectDir, GENERATED_FILES.cursorRules));
|
|
827
958
|
if (!hasExisting) return "overwrite";
|
|
@@ -840,7 +971,7 @@ async function selectConflictStrategy(projectDir) {
|
|
|
840
971
|
default: "overwrite"
|
|
841
972
|
});
|
|
842
973
|
}
|
|
843
|
-
async function generate(projectDir, scan, tools, conflict) {
|
|
974
|
+
async function generate(projectDir, scan, tools, conflict, opts) {
|
|
844
975
|
const result = {
|
|
845
976
|
claudeMd: false,
|
|
846
977
|
cursorRules: false,
|
|
@@ -852,29 +983,28 @@ async function generate(projectDir, scan, tools, conflict) {
|
|
|
852
983
|
if (tools.claude) {
|
|
853
984
|
const claudeMdPath = path15.join(projectDir, GENERATED_FILES.claudeMd);
|
|
854
985
|
if (conflict === "overwrite" || !fileExists(claudeMdPath)) {
|
|
855
|
-
const content = generateClaudeMd(scan);
|
|
856
|
-
await
|
|
986
|
+
const content = generateClaudeMd(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
987
|
+
await fs6.writeFile(claudeMdPath, content, "utf-8");
|
|
857
988
|
result.claudeMd = true;
|
|
858
989
|
} else {
|
|
859
990
|
logWarning("CLAUDE.md exists, skipping");
|
|
860
991
|
}
|
|
861
|
-
result.commands = await
|
|
862
|
-
await fs5.ensureDir(path15.join(projectDir, ".claude", "commands"));
|
|
992
|
+
result.commands = await copySkills(projectDir);
|
|
863
993
|
}
|
|
864
994
|
if (tools.cursor) {
|
|
865
995
|
const cursorPath = path15.join(projectDir, GENERATED_FILES.cursorRules);
|
|
866
996
|
if (conflict === "overwrite" || !fileExists(cursorPath)) {
|
|
867
|
-
const content = generateCursorRules(scan);
|
|
868
|
-
await
|
|
997
|
+
const content = generateCursorRules(scan, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
998
|
+
await fs6.writeFile(cursorPath, content, "utf-8");
|
|
869
999
|
result.cursorRules = true;
|
|
870
1000
|
} else {
|
|
871
1001
|
logWarning(".cursorrules exists, skipping");
|
|
872
1002
|
}
|
|
873
1003
|
const mdcDir = path15.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
874
|
-
await
|
|
1004
|
+
await fs6.ensureDir(mdcDir);
|
|
875
1005
|
const mdcFiles = generateMdcFiles(scan);
|
|
876
1006
|
for (const mdc of mdcFiles) {
|
|
877
|
-
await
|
|
1007
|
+
await fs6.writeFile(path15.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
878
1008
|
}
|
|
879
1009
|
result.cursorMdcFiles = mdcFiles.length;
|
|
880
1010
|
}
|
|
@@ -883,8 +1013,8 @@ async function generate(projectDir, scan, tools, conflict) {
|
|
|
883
1013
|
const templates = [];
|
|
884
1014
|
if (result.claudeMd) templates.push("CLAUDE.md");
|
|
885
1015
|
if (result.cursorRules) templates.push(".cursorrules");
|
|
886
|
-
const config = generateConfig(scan, templates, result.commands, result.guides);
|
|
887
|
-
await
|
|
1016
|
+
const config = generateConfig(scan, templates, result.commands, result.guides, { strictness: opts?.strictness, customFragments: opts?.customFragments });
|
|
1017
|
+
await fs6.writeJson(
|
|
888
1018
|
path15.join(projectDir, AI_KIT_CONFIG_FILE),
|
|
889
1019
|
config,
|
|
890
1020
|
{ spaces: 2 }
|
|
@@ -962,7 +1092,7 @@ function showRecommendations(scan) {
|
|
|
962
1092
|
|
|
963
1093
|
// src/commands/update.ts
|
|
964
1094
|
import path16 from "path";
|
|
965
|
-
import
|
|
1095
|
+
import fs7 from "fs-extra";
|
|
966
1096
|
import ora2 from "ora";
|
|
967
1097
|
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
968
1098
|
async function updateCommand(targetPath) {
|
|
@@ -993,44 +1123,47 @@ async function updateCommand(targetPath) {
|
|
|
993
1123
|
const scan = await scanProject(projectDir);
|
|
994
1124
|
spinner.succeed("Project re-scanned");
|
|
995
1125
|
logSection("Updating Files");
|
|
1126
|
+
const strictness = existingConfig.strictness || "standard";
|
|
1127
|
+
const customFragments = loadCustomFragments(projectDir);
|
|
1128
|
+
const genOpts = { strictness, customFragments };
|
|
996
1129
|
const templates = [];
|
|
997
1130
|
if (existingConfig.templates.includes("CLAUDE.md") || fileExists(path16.join(projectDir, GENERATED_FILES.claudeMd))) {
|
|
998
1131
|
const claudeMdPath = path16.join(projectDir, GENERATED_FILES.claudeMd);
|
|
999
|
-
const newContent = generateClaudeMd(scan);
|
|
1132
|
+
const newContent = generateClaudeMd(scan, genOpts);
|
|
1000
1133
|
const existing = readFileSafe(claudeMdPath);
|
|
1001
1134
|
if (existing) {
|
|
1002
|
-
await
|
|
1135
|
+
await fs7.writeFile(claudeMdPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1003
1136
|
} else {
|
|
1004
|
-
await
|
|
1137
|
+
await fs7.writeFile(claudeMdPath, newContent, "utf-8");
|
|
1005
1138
|
}
|
|
1006
1139
|
templates.push("CLAUDE.md");
|
|
1007
1140
|
logSuccess("CLAUDE.md updated");
|
|
1008
1141
|
}
|
|
1009
1142
|
if (existingConfig.templates.includes(".cursorrules") || fileExists(path16.join(projectDir, GENERATED_FILES.cursorRules))) {
|
|
1010
1143
|
const cursorRulesPath = path16.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1011
|
-
const newContent = generateCursorRules(scan);
|
|
1144
|
+
const newContent = generateCursorRules(scan, genOpts);
|
|
1012
1145
|
const existing = readFileSafe(cursorRulesPath);
|
|
1013
1146
|
if (existing) {
|
|
1014
|
-
await
|
|
1147
|
+
await fs7.writeFile(cursorRulesPath, mergeWithMarkers(existing, newContent), "utf-8");
|
|
1015
1148
|
} else {
|
|
1016
|
-
await
|
|
1149
|
+
await fs7.writeFile(cursorRulesPath, newContent, "utf-8");
|
|
1017
1150
|
}
|
|
1018
1151
|
templates.push(".cursorrules");
|
|
1019
1152
|
logSuccess(".cursorrules updated");
|
|
1020
1153
|
const mdcDir = path16.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1021
|
-
await
|
|
1154
|
+
await fs7.ensureDir(mdcDir);
|
|
1022
1155
|
const mdcFiles = generateMdcFiles(scan);
|
|
1023
1156
|
for (const mdc of mdcFiles) {
|
|
1024
|
-
await
|
|
1157
|
+
await fs7.writeFile(path16.join(mdcDir, mdc.filename), mdc.content, "utf-8");
|
|
1025
1158
|
}
|
|
1026
1159
|
logSuccess(`${mdcFiles.length} .cursor/rules/*.mdc files updated`);
|
|
1027
1160
|
}
|
|
1028
|
-
const commands = await
|
|
1029
|
-
logSuccess(`${commands.length}
|
|
1161
|
+
const commands = await copySkills(projectDir);
|
|
1162
|
+
logSuccess(`${commands.length} skills updated (.claude/skills/ + .cursor/skills/)`);
|
|
1030
1163
|
const guides = await copyGuides(projectDir);
|
|
1031
1164
|
logSuccess(`${guides.length} guides updated`);
|
|
1032
|
-
const config = generateConfig(scan, templates, commands, guides);
|
|
1033
|
-
await
|
|
1165
|
+
const config = generateConfig(scan, templates, commands, guides, genOpts);
|
|
1166
|
+
await fs7.writeJson(configPath, config, { spaces: 2 });
|
|
1034
1167
|
logSuccess("ai-kit.config.json updated");
|
|
1035
1168
|
console.log("");
|
|
1036
1169
|
logInfo("All AI configs refreshed with latest project scan.");
|
|
@@ -1038,7 +1171,7 @@ async function updateCommand(targetPath) {
|
|
|
1038
1171
|
|
|
1039
1172
|
// src/commands/reset.ts
|
|
1040
1173
|
import path17 from "path";
|
|
1041
|
-
import
|
|
1174
|
+
import fs8 from "fs-extra";
|
|
1042
1175
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1043
1176
|
async function resetCommand(targetPath) {
|
|
1044
1177
|
const projectDir = path17.resolve(targetPath || process.cwd());
|
|
@@ -1048,6 +1181,8 @@ async function resetCommand(targetPath) {
|
|
|
1048
1181
|
logInfo(` - ${GENERATED_FILES.cursorRules}`);
|
|
1049
1182
|
logInfo(` - ${GENERATED_FILES.cursorMdcDir}/`);
|
|
1050
1183
|
logInfo(` - ${GENERATED_FILES.claudeCommands}/`);
|
|
1184
|
+
logInfo(` - ${GENERATED_FILES.claudeSkills}/`);
|
|
1185
|
+
logInfo(` - ${GENERATED_FILES.cursorSkills}/`);
|
|
1051
1186
|
logInfo(` - ai-kit/`);
|
|
1052
1187
|
logInfo(` - ${AI_KIT_CONFIG_FILE}`);
|
|
1053
1188
|
console.log("");
|
|
@@ -1062,32 +1197,42 @@ async function resetCommand(targetPath) {
|
|
|
1062
1197
|
const removed = [];
|
|
1063
1198
|
const claudeMdPath = path17.join(projectDir, GENERATED_FILES.claudeMd);
|
|
1064
1199
|
if (fileExists(claudeMdPath)) {
|
|
1065
|
-
await
|
|
1200
|
+
await fs8.remove(claudeMdPath);
|
|
1066
1201
|
removed.push(GENERATED_FILES.claudeMd);
|
|
1067
1202
|
}
|
|
1068
1203
|
const cursorPath = path17.join(projectDir, GENERATED_FILES.cursorRules);
|
|
1069
1204
|
if (fileExists(cursorPath)) {
|
|
1070
|
-
await
|
|
1205
|
+
await fs8.remove(cursorPath);
|
|
1071
1206
|
removed.push(GENERATED_FILES.cursorRules);
|
|
1072
1207
|
}
|
|
1073
1208
|
const cursorMdcDir = path17.join(projectDir, GENERATED_FILES.cursorMdcDir);
|
|
1074
1209
|
if (fileExists(cursorMdcDir)) {
|
|
1075
|
-
await
|
|
1210
|
+
await fs8.remove(cursorMdcDir);
|
|
1076
1211
|
removed.push(GENERATED_FILES.cursorMdcDir);
|
|
1077
1212
|
}
|
|
1078
1213
|
const commandsDir = path17.join(projectDir, GENERATED_FILES.claudeCommands);
|
|
1079
1214
|
if (fileExists(commandsDir)) {
|
|
1080
|
-
await
|
|
1215
|
+
await fs8.remove(commandsDir);
|
|
1081
1216
|
removed.push(GENERATED_FILES.claudeCommands);
|
|
1082
1217
|
}
|
|
1218
|
+
const claudeSkillsDir = path17.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
1219
|
+
if (fileExists(claudeSkillsDir)) {
|
|
1220
|
+
await fs8.remove(claudeSkillsDir);
|
|
1221
|
+
removed.push(GENERATED_FILES.claudeSkills);
|
|
1222
|
+
}
|
|
1223
|
+
const cursorSkillsDir = path17.join(projectDir, GENERATED_FILES.cursorSkills);
|
|
1224
|
+
if (fileExists(cursorSkillsDir)) {
|
|
1225
|
+
await fs8.remove(cursorSkillsDir);
|
|
1226
|
+
removed.push(GENERATED_FILES.cursorSkills);
|
|
1227
|
+
}
|
|
1083
1228
|
const aiKitDir = path17.join(projectDir, "ai-kit");
|
|
1084
1229
|
if (fileExists(aiKitDir)) {
|
|
1085
|
-
await
|
|
1230
|
+
await fs8.remove(aiKitDir);
|
|
1086
1231
|
removed.push("ai-kit/");
|
|
1087
1232
|
}
|
|
1088
1233
|
const configPath = path17.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1089
1234
|
if (fileExists(configPath)) {
|
|
1090
|
-
await
|
|
1235
|
+
await fs8.remove(configPath);
|
|
1091
1236
|
removed.push(AI_KIT_CONFIG_FILE);
|
|
1092
1237
|
}
|
|
1093
1238
|
logSection("Reset Complete");
|
|
@@ -1100,7 +1245,7 @@ async function resetCommand(targetPath) {
|
|
|
1100
1245
|
|
|
1101
1246
|
// src/commands/tokens.ts
|
|
1102
1247
|
import path18 from "path";
|
|
1103
|
-
import
|
|
1248
|
+
import fs9 from "fs-extra";
|
|
1104
1249
|
import chalk2 from "chalk";
|
|
1105
1250
|
import ora3 from "ora";
|
|
1106
1251
|
import os from "os";
|
|
@@ -1111,11 +1256,11 @@ var PRICING = {
|
|
|
1111
1256
|
var PLAN_BUDGET = 20;
|
|
1112
1257
|
function findSessionFiles() {
|
|
1113
1258
|
const claudeDir = path18.join(os.homedir(), ".claude", "projects");
|
|
1114
|
-
if (!
|
|
1259
|
+
if (!fs9.existsSync(claudeDir)) return [];
|
|
1115
1260
|
const files = [];
|
|
1116
1261
|
function walkDir(dir) {
|
|
1117
1262
|
try {
|
|
1118
|
-
const entries =
|
|
1263
|
+
const entries = fs9.readdirSync(dir, { withFileTypes: true });
|
|
1119
1264
|
for (const entry of entries) {
|
|
1120
1265
|
const full = path18.join(dir, entry.name);
|
|
1121
1266
|
if (entry.isDirectory()) {
|
|
@@ -1132,7 +1277,7 @@ function findSessionFiles() {
|
|
|
1132
1277
|
}
|
|
1133
1278
|
function parseSessionFile(filePath) {
|
|
1134
1279
|
try {
|
|
1135
|
-
const content =
|
|
1280
|
+
const content = fs9.readFileSync(filePath, "utf-8");
|
|
1136
1281
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
1137
1282
|
const usage = {
|
|
1138
1283
|
inputTokens: 0,
|
|
@@ -1169,17 +1314,19 @@ function parseSessionFile(filePath) {
|
|
|
1169
1314
|
}
|
|
1170
1315
|
if (usage.inputTokens === 0 && usage.outputTokens === 0) return null;
|
|
1171
1316
|
if (!sessionDate) {
|
|
1172
|
-
const stat =
|
|
1317
|
+
const stat = fs9.statSync(filePath);
|
|
1173
1318
|
sessionDate = stat.mtime.toISOString().slice(0, 10);
|
|
1174
1319
|
}
|
|
1175
1320
|
const sessionId = path18.basename(filePath, ".jsonl");
|
|
1321
|
+
const projectName = path18.basename(path18.dirname(filePath));
|
|
1176
1322
|
return {
|
|
1177
1323
|
sessionId,
|
|
1178
1324
|
filePath,
|
|
1179
1325
|
date: sessionDate,
|
|
1180
1326
|
usage,
|
|
1181
1327
|
model,
|
|
1182
|
-
messageCount
|
|
1328
|
+
messageCount,
|
|
1329
|
+
projectName
|
|
1183
1330
|
};
|
|
1184
1331
|
} catch {
|
|
1185
1332
|
return null;
|
|
@@ -1255,8 +1402,70 @@ ${chalk2.bold(label)}`);
|
|
|
1255
1402
|
console.log(` Cache read: ${chalk2.white(fmt(usage.cacheReadTokens))}`);
|
|
1256
1403
|
console.log(` Estimated cost: ${chalk2.yellow("~" + fmtCost(cost))}`);
|
|
1257
1404
|
}
|
|
1405
|
+
function printProjectBreakdown(sessions) {
|
|
1406
|
+
const projectMap = /* @__PURE__ */ new Map();
|
|
1407
|
+
for (const s of sessions) {
|
|
1408
|
+
const existing = projectMap.get(s.projectName) || { sessions: 0, cost: 0 };
|
|
1409
|
+
existing.sessions++;
|
|
1410
|
+
existing.cost += calculateCost(s.usage, s.model);
|
|
1411
|
+
projectMap.set(s.projectName, existing);
|
|
1412
|
+
}
|
|
1413
|
+
const sorted = Array.from(projectMap.entries()).sort((a, b) => b[1].cost - a[1].cost);
|
|
1414
|
+
console.log(`
|
|
1415
|
+
${chalk2.bold("Per-Project Breakdown")}`);
|
|
1416
|
+
for (const [name, data] of sorted.slice(0, 10)) {
|
|
1417
|
+
const bar = progressBar(data.cost / sumCost(sessions) * 100, 15);
|
|
1418
|
+
console.log(` ${bar} ${chalk2.yellow(fmtCost(data.cost))} ${chalk2.dim(`(${data.sessions} sessions)`)} ${name}`);
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function printTrendAnalysis(sessions) {
|
|
1422
|
+
const today = /* @__PURE__ */ new Date();
|
|
1423
|
+
const thisWeekStart = new Date(today);
|
|
1424
|
+
thisWeekStart.setDate(today.getDate() - today.getDay() + (today.getDay() === 0 ? -6 : 1));
|
|
1425
|
+
const thisWeekStr = thisWeekStart.toISOString().slice(0, 10);
|
|
1426
|
+
const lastWeekStart = new Date(thisWeekStart);
|
|
1427
|
+
lastWeekStart.setDate(lastWeekStart.getDate() - 7);
|
|
1428
|
+
const lastWeekEnd = new Date(thisWeekStart);
|
|
1429
|
+
lastWeekEnd.setDate(lastWeekEnd.getDate() - 1);
|
|
1430
|
+
const lastWeekStartStr = lastWeekStart.toISOString().slice(0, 10);
|
|
1431
|
+
const lastWeekEndStr = lastWeekEnd.toISOString().slice(0, 10);
|
|
1432
|
+
const todayStr = today.toISOString().slice(0, 10);
|
|
1433
|
+
const thisWeekSessions = filterDateRange(sessions, thisWeekStr, todayStr);
|
|
1434
|
+
const lastWeekSessions = filterDateRange(sessions, lastWeekStartStr, lastWeekEndStr);
|
|
1435
|
+
const thisWeekCost = sumCost(thisWeekSessions);
|
|
1436
|
+
const lastWeekCost = sumCost(lastWeekSessions);
|
|
1437
|
+
if (lastWeekCost > 0) {
|
|
1438
|
+
const change = (thisWeekCost - lastWeekCost) / lastWeekCost * 100;
|
|
1439
|
+
const arrow = change > 0 ? chalk2.red("\u2191") : change < 0 ? chalk2.green("\u2193") : chalk2.dim("\u2192");
|
|
1440
|
+
const changeStr = change > 0 ? `+${change.toFixed(0)}%` : `${change.toFixed(0)}%`;
|
|
1441
|
+
console.log(`
|
|
1442
|
+
${chalk2.bold("Week-over-Week Trend")}`);
|
|
1443
|
+
console.log(` Last week: ${chalk2.yellow(fmtCost(lastWeekCost))} (${lastWeekSessions.length} sessions)`);
|
|
1444
|
+
console.log(` This week: ${chalk2.yellow(fmtCost(thisWeekCost))} (${thisWeekSessions.length} sessions)`);
|
|
1445
|
+
console.log(` Change: ${arrow} ${changeStr}`);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
function printRoiEstimate(sessions, monthCost) {
|
|
1449
|
+
const AVG_MINUTES_SAVED_PER_SESSION = 15;
|
|
1450
|
+
const DEVELOPER_HOURLY_RATE = 75;
|
|
1451
|
+
const totalSessions = sessions.length;
|
|
1452
|
+
const minutesSaved = totalSessions * AVG_MINUTES_SAVED_PER_SESSION;
|
|
1453
|
+
const hoursSaved = minutesSaved / 60;
|
|
1454
|
+
const valueSaved = hoursSaved * DEVELOPER_HOURLY_RATE;
|
|
1455
|
+
const roi = monthCost > 0 ? (valueSaved - monthCost) / monthCost * 100 : 0;
|
|
1456
|
+
console.log(`
|
|
1457
|
+
${chalk2.bold("ROI Estimate")} ${chalk2.dim("(based on ~15 min saved per session)")}`);
|
|
1458
|
+
console.log(` Sessions this month: ${chalk2.cyan(String(totalSessions))}`);
|
|
1459
|
+
console.log(` Estimated time saved: ${chalk2.cyan(`${hoursSaved.toFixed(1)} hours`)} (${minutesSaved} min)`);
|
|
1460
|
+
console.log(` Estimated value saved: ${chalk2.green(fmtCost(valueSaved))} (at $${DEVELOPER_HOURLY_RATE}/hr)`);
|
|
1461
|
+
console.log(` AI cost: ${chalk2.yellow(fmtCost(monthCost))}`);
|
|
1462
|
+
if (roi > 0) {
|
|
1463
|
+
console.log(` ROI: ${chalk2.green(`${roi.toFixed(0)}%`)}`);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1258
1466
|
async function tokensCommand(options = {}) {
|
|
1259
1467
|
logSection("AI Kit \u2014 Token Usage");
|
|
1468
|
+
const budget = options.budget || PLAN_BUDGET;
|
|
1260
1469
|
const spinner = ora3("Scanning Claude Code session logs...").start();
|
|
1261
1470
|
const sessionFiles = findSessionFiles();
|
|
1262
1471
|
if (sessionFiles.length === 0) {
|
|
@@ -1289,7 +1498,7 @@ async function tokensCommand(options = {}) {
|
|
|
1289
1498
|
printPeriodSummary("This Week", weekSessions);
|
|
1290
1499
|
printPeriodSummary("This Month", monthSessions);
|
|
1291
1500
|
const monthCost = sumCost(monthSessions);
|
|
1292
|
-
const budgetPercent = Math.min(monthCost /
|
|
1501
|
+
const budgetPercent = Math.min(monthCost / budget * 100, 100);
|
|
1293
1502
|
const daysInMonth = new Date(
|
|
1294
1503
|
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
1295
1504
|
(/* @__PURE__ */ new Date()).getMonth() + 1,
|
|
@@ -1298,16 +1507,37 @@ async function tokensCommand(options = {}) {
|
|
|
1298
1507
|
const dayOfMonth = (/* @__PURE__ */ new Date()).getDate();
|
|
1299
1508
|
const daysRemaining = daysInMonth - dayOfMonth;
|
|
1300
1509
|
const dailyAvg = dayOfMonth > 0 ? monthCost / dayOfMonth : 0;
|
|
1301
|
-
const estimatedDaysLeft = dailyAvg > 0 ? Math.floor((
|
|
1510
|
+
const estimatedDaysLeft = dailyAvg > 0 ? Math.floor((budget - monthCost) / dailyAvg) : daysRemaining;
|
|
1302
1511
|
console.log(`
|
|
1303
|
-
${chalk2.bold(`$${
|
|
1512
|
+
${chalk2.bold(`$${budget} Plan Budget`)}`);
|
|
1304
1513
|
console.log(
|
|
1305
|
-
` ${progressBar(budgetPercent)} ${Math.round(budgetPercent)}% used (~${fmtCost(monthCost)} of ${fmtCost(
|
|
1514
|
+
` ${progressBar(budgetPercent)} ${Math.round(budgetPercent)}% used (~${fmtCost(monthCost)} of ${fmtCost(budget)})`
|
|
1306
1515
|
);
|
|
1307
1516
|
console.log(
|
|
1308
1517
|
` Estimated days remaining at this rate: ${chalk2.cyan(String(Math.max(estimatedDaysLeft, 0)))} days`
|
|
1309
1518
|
);
|
|
1310
1519
|
console.log(` Daily average: ${chalk2.yellow(fmtCost(dailyAvg))}`);
|
|
1520
|
+
if (budgetPercent >= 90) {
|
|
1521
|
+
console.log(chalk2.red.bold("\n \u26A0 ALERT: Over 90% of monthly budget used!"));
|
|
1522
|
+
} else if (budgetPercent >= 75) {
|
|
1523
|
+
console.log(chalk2.yellow("\n \u26A0 Warning: Over 75% of monthly budget used."));
|
|
1524
|
+
} else if (budgetPercent >= 50) {
|
|
1525
|
+
console.log(chalk2.blue("\n \u2139 Note: Over 50% of monthly budget used."));
|
|
1526
|
+
}
|
|
1527
|
+
const opusSessions = monthSessions.filter((s) => s.model === "opus");
|
|
1528
|
+
const sonnetSessions = monthSessions.filter((s) => s.model === "sonnet");
|
|
1529
|
+
const opusCost = sumCost(opusSessions);
|
|
1530
|
+
const sonnetCost = sumCost(sonnetSessions);
|
|
1531
|
+
if (opusCost > sonnetCost * 2 && opusSessions.length > 5) {
|
|
1532
|
+
console.log(`
|
|
1533
|
+
${chalk2.bold("Model Recommendation")}`);
|
|
1534
|
+
console.log(` Opus usage: ${chalk2.yellow(fmtCost(opusCost))} (${opusSessions.length} sessions)`);
|
|
1535
|
+
console.log(` Sonnet usage: ${chalk2.yellow(fmtCost(sonnetCost))} (${sonnetSessions.length} sessions)`);
|
|
1536
|
+
console.log(chalk2.dim(" Tip: Use Sonnet for routine tasks (reviews, tests, docs) and Opus for complex tasks (architecture, debugging)."));
|
|
1537
|
+
}
|
|
1538
|
+
printProjectBreakdown(monthSessions);
|
|
1539
|
+
printTrendAnalysis(sessions);
|
|
1540
|
+
printRoiEstimate(monthSessions, monthCost);
|
|
1311
1541
|
console.log(
|
|
1312
1542
|
`
|
|
1313
1543
|
${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
|
|
@@ -1316,6 +1546,16 @@ ${chalk2.dim("Tip: Use /understand before modifying unfamiliar code \u2014")}`
|
|
|
1316
1546
|
chalk2.dim(" it's cheaper than a failed implementation attempt.")
|
|
1317
1547
|
);
|
|
1318
1548
|
console.log("");
|
|
1549
|
+
if (options.csv) {
|
|
1550
|
+
const csvPath = path18.join(process.cwd(), "token-usage.csv");
|
|
1551
|
+
const csvHeader = "Date,Sessions,Input Tokens,Output Tokens,Cache Tokens,Cost\n";
|
|
1552
|
+
const daily = aggregateByDate(sessions);
|
|
1553
|
+
const csvRows = daily.map(
|
|
1554
|
+
(d) => `${d.date},${d.sessions},${d.usage.inputTokens},${d.usage.outputTokens},${d.usage.cacheReadTokens},${d.cost.toFixed(2)}`
|
|
1555
|
+
).join("\n");
|
|
1556
|
+
await fs9.writeFile(csvPath, csvHeader + csvRows, "utf-8");
|
|
1557
|
+
logSuccess(`CSV exported to ${csvPath}`);
|
|
1558
|
+
}
|
|
1319
1559
|
if (options.export) {
|
|
1320
1560
|
await exportDashboard(sessions, todaySessions, weekSessions, monthSessions);
|
|
1321
1561
|
}
|
|
@@ -1353,10 +1593,10 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1353
1593
|
const dataPath = path18.join(outputDir, "token-data.json");
|
|
1354
1594
|
const dashboardSrc = path18.join(PACKAGE_ROOT, "templates", "token-dashboard.html");
|
|
1355
1595
|
const dashboardDest = path18.join(outputDir, "token-dashboard.html");
|
|
1356
|
-
await
|
|
1596
|
+
await fs9.writeJson(dataPath, exportData, { spaces: 2 });
|
|
1357
1597
|
logInfo(`Token data written to ${dataPath}`);
|
|
1358
|
-
if (await
|
|
1359
|
-
await
|
|
1598
|
+
if (await fs9.pathExists(dashboardSrc)) {
|
|
1599
|
+
await fs9.copy(dashboardSrc, dashboardDest, { overwrite: true });
|
|
1360
1600
|
logInfo(`Dashboard copied to ${dashboardDest}`);
|
|
1361
1601
|
} else {
|
|
1362
1602
|
logWarning("Dashboard template not found. Skipping HTML export.");
|
|
@@ -1372,6 +1612,739 @@ async function exportDashboard(allSessions, todaySessions, weekSessions, monthSe
|
|
|
1372
1612
|
}
|
|
1373
1613
|
}
|
|
1374
1614
|
|
|
1615
|
+
// src/commands/doctor.ts
|
|
1616
|
+
import path19 from "path";
|
|
1617
|
+
import chalk3 from "chalk";
|
|
1618
|
+
import ora4 from "ora";
|
|
1619
|
+
async function doctorCommand(targetPath) {
|
|
1620
|
+
const projectDir = path19.resolve(targetPath || process.cwd());
|
|
1621
|
+
const configPath = path19.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1622
|
+
let passed = 0;
|
|
1623
|
+
let warnings = 0;
|
|
1624
|
+
let issues = 0;
|
|
1625
|
+
logSection("AI Kit \u2014 Doctor");
|
|
1626
|
+
console.log("");
|
|
1627
|
+
if (!fileExists(configPath)) {
|
|
1628
|
+
logError("ai-kit.config.json not found. Run `ai-kit init` first.");
|
|
1629
|
+
issues++;
|
|
1630
|
+
showSummary(passed, warnings, issues);
|
|
1631
|
+
return;
|
|
1632
|
+
}
|
|
1633
|
+
const config = readJsonSafe(configPath);
|
|
1634
|
+
if (!config) {
|
|
1635
|
+
logError("ai-kit.config.json is corrupted or unreadable. Run `ai-kit init` to re-initialize.");
|
|
1636
|
+
issues++;
|
|
1637
|
+
showSummary(passed, warnings, issues);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
logSuccess(`ai-kit.config.json found (v${config.version})`);
|
|
1641
|
+
passed++;
|
|
1642
|
+
if (config.version !== VERSION) {
|
|
1643
|
+
logWarning(
|
|
1644
|
+
`Config version mismatch \u2014 config is v${config.version}, CLI is v${VERSION}. Run \`ai-kit update\` to sync.`
|
|
1645
|
+
);
|
|
1646
|
+
warnings++;
|
|
1647
|
+
} else {
|
|
1648
|
+
logSuccess(`Version matches CLI (v${VERSION})`);
|
|
1649
|
+
passed++;
|
|
1650
|
+
}
|
|
1651
|
+
for (const template of config.templates) {
|
|
1652
|
+
const templateFile = template === "CLAUDE.md" ? GENERATED_FILES.claudeMd : template === ".cursorrules" ? GENERATED_FILES.cursorRules : template;
|
|
1653
|
+
const templatePath = path19.join(projectDir, templateFile);
|
|
1654
|
+
if (fileExists(templatePath)) {
|
|
1655
|
+
logSuccess(`${template} exists and in sync`);
|
|
1656
|
+
passed++;
|
|
1657
|
+
} else {
|
|
1658
|
+
logError(`${template} is listed in config but missing from disk`);
|
|
1659
|
+
issues++;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
const missingSkills = [];
|
|
1663
|
+
for (const skill of config.commands) {
|
|
1664
|
+
const claudeSkillPath = path19.join(projectDir, GENERATED_FILES.claudeSkills, skill);
|
|
1665
|
+
const cursorSkillPath = path19.join(projectDir, GENERATED_FILES.cursorSkills, skill);
|
|
1666
|
+
if (!fileExists(claudeSkillPath) && !fileExists(cursorSkillPath)) {
|
|
1667
|
+
missingSkills.push(skill);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
if (missingSkills.length === 0) {
|
|
1671
|
+
logSuccess(`${config.commands.length}/${config.commands.length} skills present`);
|
|
1672
|
+
passed++;
|
|
1673
|
+
} else {
|
|
1674
|
+
logError(
|
|
1675
|
+
`${config.commands.length - missingSkills.length}/${config.commands.length} skills present \u2014 missing: ${missingSkills.join(", ")}`
|
|
1676
|
+
);
|
|
1677
|
+
issues++;
|
|
1678
|
+
}
|
|
1679
|
+
const guidesDir = path19.join(projectDir, "ai-kit", "guides");
|
|
1680
|
+
const missingGuides = [];
|
|
1681
|
+
for (const guide of config.guides) {
|
|
1682
|
+
const guidePath = path19.join(guidesDir, guide);
|
|
1683
|
+
if (!fileExists(guidePath)) {
|
|
1684
|
+
missingGuides.push(guide);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
if (missingGuides.length === 0) {
|
|
1688
|
+
logSuccess(`${config.guides.length}/${config.guides.length} guides present`);
|
|
1689
|
+
passed++;
|
|
1690
|
+
} else {
|
|
1691
|
+
logError(
|
|
1692
|
+
`${config.guides.length - missingGuides.length}/${config.guides.length} guides present \u2014 missing: ${missingGuides.join(", ")}`
|
|
1693
|
+
);
|
|
1694
|
+
issues++;
|
|
1695
|
+
}
|
|
1696
|
+
const spinner = ora4("Running fresh project scan...").start();
|
|
1697
|
+
let freshScan;
|
|
1698
|
+
try {
|
|
1699
|
+
freshScan = await scanProject(projectDir);
|
|
1700
|
+
spinner.succeed("Project re-scanned");
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
spinner.fail("Failed to scan project");
|
|
1703
|
+
logWarning(`Could not run staleness check: ${String(err)}`);
|
|
1704
|
+
warnings++;
|
|
1705
|
+
freshScan = config.scanResult;
|
|
1706
|
+
}
|
|
1707
|
+
const stalenessWarnings = compareScanResults(config.scanResult, freshScan);
|
|
1708
|
+
if (stalenessWarnings.length === 0) {
|
|
1709
|
+
logSuccess("Config is up to date with project state");
|
|
1710
|
+
passed++;
|
|
1711
|
+
} else {
|
|
1712
|
+
for (const warning of stalenessWarnings) {
|
|
1713
|
+
logWarning(warning);
|
|
1714
|
+
warnings++;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
const mcpChecks = [
|
|
1718
|
+
{ name: "Playwright MCP", key: "playwright" },
|
|
1719
|
+
{ name: "Context7 MCP", key: "context7" },
|
|
1720
|
+
{ name: "GitHub MCP", key: "github" },
|
|
1721
|
+
{ name: "Perplexity MCP", key: "perplexity" },
|
|
1722
|
+
{ name: "Figma MCP", key: "figma" }
|
|
1723
|
+
];
|
|
1724
|
+
for (const mcp of mcpChecks) {
|
|
1725
|
+
if (freshScan.mcpServers[mcp.key]) {
|
|
1726
|
+
logSuccess(`${mcp.name} configured`);
|
|
1727
|
+
passed++;
|
|
1728
|
+
} else {
|
|
1729
|
+
logWarning(`${mcp.name} not configured`);
|
|
1730
|
+
warnings++;
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
const toolChecks = [
|
|
1734
|
+
{ name: "Playwright", key: "playwright", hint: "npm install -D @playwright/test" },
|
|
1735
|
+
{ name: "ESLint", key: "eslint", hint: "npm install -D eslint" },
|
|
1736
|
+
{ name: "Prettier", key: "prettier", hint: "npm install -D prettier" },
|
|
1737
|
+
{ name: "axe-core", key: "axeCore", hint: "npm install -D @axe-core/playwright" },
|
|
1738
|
+
{ name: "Knip", key: "knip", hint: "npm install -D knip" },
|
|
1739
|
+
{ name: "Bundle Analyzer", key: "bundleAnalyzer", hint: "npm install -D @next/bundle-analyzer" }
|
|
1740
|
+
];
|
|
1741
|
+
for (const tool of toolChecks) {
|
|
1742
|
+
if (freshScan.tools[tool.key]) {
|
|
1743
|
+
logSuccess(`${tool.name} detected`);
|
|
1744
|
+
passed++;
|
|
1745
|
+
} else {
|
|
1746
|
+
logError(`${tool.name} not found \u2014 recommend installing: ${tool.hint}`);
|
|
1747
|
+
issues++;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
console.log("");
|
|
1751
|
+
showSummary(passed, warnings, issues);
|
|
1752
|
+
}
|
|
1753
|
+
function showSummary(passed, warnings, issues) {
|
|
1754
|
+
const parts = [];
|
|
1755
|
+
parts.push(chalk3.green(`${passed} passed`));
|
|
1756
|
+
if (warnings > 0) parts.push(chalk3.yellow(`${warnings} warnings`));
|
|
1757
|
+
if (issues > 0) parts.push(chalk3.red(`${issues} issues`));
|
|
1758
|
+
console.log(chalk3.bold(`Summary: ${parts.join(", ")}`));
|
|
1759
|
+
}
|
|
1760
|
+
function compareScanResults(previous, current) {
|
|
1761
|
+
const warnings = [];
|
|
1762
|
+
if (previous.framework !== current.framework) {
|
|
1763
|
+
warnings.push(
|
|
1764
|
+
`Stack may have changed \u2014 framework was ${previous.framework}, now ${current.framework}`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
if (previous.nextjsVersion && current.nextjsVersion && previous.nextjsVersion !== current.nextjsVersion) {
|
|
1768
|
+
warnings.push(
|
|
1769
|
+
`Next.js version changed: ${previous.nextjsVersion} \u2192 ${current.nextjsVersion}`
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
if (previous.routerType && current.routerType && previous.routerType !== current.routerType) {
|
|
1773
|
+
warnings.push(
|
|
1774
|
+
`Router type changed: ${previous.routerType} \u2192 ${current.routerType}`
|
|
1775
|
+
);
|
|
1776
|
+
}
|
|
1777
|
+
const prevStyles = new Set(previous.styling);
|
|
1778
|
+
const currStyles = new Set(current.styling);
|
|
1779
|
+
for (const style of currStyles) {
|
|
1780
|
+
if (!prevStyles.has(style)) {
|
|
1781
|
+
warnings.push(`Stack may have changed \u2014 detected new styling: ${style}`);
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
for (const style of prevStyles) {
|
|
1785
|
+
if (!currStyles.has(style)) {
|
|
1786
|
+
warnings.push(`Stack may have changed \u2014 styling removed: ${style}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if (previous.typescript !== current.typescript) {
|
|
1790
|
+
warnings.push(
|
|
1791
|
+
`TypeScript ${current.typescript ? "detected (was not before)" : "no longer detected"}`
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
if (previous.monorepo !== current.monorepo) {
|
|
1795
|
+
warnings.push(
|
|
1796
|
+
`Monorepo ${current.monorepo ? "detected (was not before)" : "no longer detected"}`
|
|
1797
|
+
);
|
|
1798
|
+
}
|
|
1799
|
+
if (previous.packageManager !== current.packageManager) {
|
|
1800
|
+
warnings.push(
|
|
1801
|
+
`Package manager changed: ${previous.packageManager} \u2192 ${current.packageManager}`
|
|
1802
|
+
);
|
|
1803
|
+
}
|
|
1804
|
+
const toolKeys = Object.keys(current.tools);
|
|
1805
|
+
for (const key of toolKeys) {
|
|
1806
|
+
if (current.tools[key] && !previous.tools[key]) {
|
|
1807
|
+
warnings.push(`Stack may have changed \u2014 detected new tool: ${key}`);
|
|
1808
|
+
}
|
|
1809
|
+
if (!current.tools[key] && previous.tools[key]) {
|
|
1810
|
+
warnings.push(`Stack may have changed \u2014 tool removed: ${key}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
if (previous.cms !== current.cms) {
|
|
1814
|
+
warnings.push(`CMS changed: ${previous.cms} \u2192 ${current.cms}`);
|
|
1815
|
+
}
|
|
1816
|
+
return warnings;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// src/commands/diff.ts
|
|
1820
|
+
import path20 from "path";
|
|
1821
|
+
import fs10 from "fs-extra";
|
|
1822
|
+
import chalk4 from "chalk";
|
|
1823
|
+
import ora5 from "ora";
|
|
1824
|
+
async function diffCommand(targetPath) {
|
|
1825
|
+
const projectDir = path20.resolve(targetPath || process.cwd());
|
|
1826
|
+
const configPath = path20.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
1827
|
+
console.log(chalk4.bold("AI Kit \u2014 Diff (dry run)\n"));
|
|
1828
|
+
if (!fileExists(configPath)) {
|
|
1829
|
+
logError("No ai-kit.config.json found. Run `ai-kit init` first.");
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
const existingConfig = readJsonSafe(configPath);
|
|
1833
|
+
if (!existingConfig) {
|
|
1834
|
+
logError("Could not read ai-kit.config.json. Run `ai-kit init` to re-initialize.");
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
const existingMajor = parseInt(existingConfig.version.split(".")[0], 10);
|
|
1838
|
+
const currentMajor = parseInt(VERSION.split(".")[0], 10);
|
|
1839
|
+
if (existingMajor !== currentMajor) {
|
|
1840
|
+
logWarning(
|
|
1841
|
+
`Config was generated with ai-kit v${existingConfig.version}, but you're running v${VERSION}. Consider running \`ai-kit init\` to re-initialize.`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
const spinner = ora5("Scanning project...").start();
|
|
1845
|
+
const newScan = await scanProject(projectDir);
|
|
1846
|
+
spinner.succeed("Project scanned");
|
|
1847
|
+
const oldScan = existingConfig.scanResult;
|
|
1848
|
+
logSection("Stack Changes");
|
|
1849
|
+
const stackChanges = diffStack(oldScan, newScan);
|
|
1850
|
+
if (stackChanges.length === 0) {
|
|
1851
|
+
console.log(chalk4.dim(" No stack changes detected"));
|
|
1852
|
+
} else {
|
|
1853
|
+
for (const change of stackChanges) {
|
|
1854
|
+
if (change.type === "added") {
|
|
1855
|
+
console.log(chalk4.green(` + ${change.label}`));
|
|
1856
|
+
} else if (change.type === "modified") {
|
|
1857
|
+
console.log(chalk4.yellow(` ~ ${change.label}`));
|
|
1858
|
+
} else {
|
|
1859
|
+
console.log(chalk4.red(` - ${change.label}`));
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
logSection("File Changes");
|
|
1864
|
+
const oldFragments = selectFragments(oldScan);
|
|
1865
|
+
const newFragments = selectFragments(newScan);
|
|
1866
|
+
let modified = 0;
|
|
1867
|
+
let added = 0;
|
|
1868
|
+
let unchanged = 0;
|
|
1869
|
+
const claudeMdStatus = diffGeneratedFile(
|
|
1870
|
+
projectDir,
|
|
1871
|
+
GENERATED_FILES.claudeMd,
|
|
1872
|
+
existingConfig,
|
|
1873
|
+
() => generateClaudeMd(newScan),
|
|
1874
|
+
oldFragments,
|
|
1875
|
+
newFragments
|
|
1876
|
+
);
|
|
1877
|
+
logFileChange(claudeMdStatus);
|
|
1878
|
+
if (claudeMdStatus.status === "modified") modified++;
|
|
1879
|
+
else if (claudeMdStatus.status === "added") added++;
|
|
1880
|
+
else unchanged++;
|
|
1881
|
+
const cursorRulesStatus = diffGeneratedFile(
|
|
1882
|
+
projectDir,
|
|
1883
|
+
GENERATED_FILES.cursorRules,
|
|
1884
|
+
existingConfig,
|
|
1885
|
+
() => generateCursorRules(newScan),
|
|
1886
|
+
oldFragments,
|
|
1887
|
+
newFragments
|
|
1888
|
+
);
|
|
1889
|
+
logFileChange(cursorRulesStatus);
|
|
1890
|
+
if (cursorRulesStatus.status === "modified") modified++;
|
|
1891
|
+
else if (cursorRulesStatus.status === "added") added++;
|
|
1892
|
+
else unchanged++;
|
|
1893
|
+
const skillsDir = path20.join(projectDir, GENERATED_FILES.claudeSkills);
|
|
1894
|
+
const skillCount = countFilesInDir(skillsDir);
|
|
1895
|
+
console.log(chalk4.dim(` unchanged ${GENERATED_FILES.claudeSkills}/ (${skillCount} skills)`));
|
|
1896
|
+
unchanged++;
|
|
1897
|
+
const guidesDir = path20.join(projectDir, AI_KIT_FOLDER_NAME, "guides");
|
|
1898
|
+
const guideCount = existingConfig.guides?.length || 0;
|
|
1899
|
+
console.log(chalk4.dim(` unchanged ai-kit/guides/ (${guideCount} guides)`));
|
|
1900
|
+
unchanged++;
|
|
1901
|
+
console.log("");
|
|
1902
|
+
const parts = [];
|
|
1903
|
+
if (modified > 0) parts.push(`${modified} files would be modified`);
|
|
1904
|
+
if (added > 0) parts.push(`${added} added`);
|
|
1905
|
+
const removed = 0;
|
|
1906
|
+
parts.push(`${removed} removed`);
|
|
1907
|
+
logInfo(`Summary: ${parts.join(", ")}`);
|
|
1908
|
+
logInfo("Run `ai-kit update` to apply these changes.");
|
|
1909
|
+
}
|
|
1910
|
+
var AI_KIT_FOLDER_NAME = "ai-kit";
|
|
1911
|
+
function diffStack(oldScan, newScan) {
|
|
1912
|
+
const changes = [];
|
|
1913
|
+
if (oldScan.framework === newScan.framework && newScan.framework === "nextjs") {
|
|
1914
|
+
if (oldScan.nextjsVersion !== newScan.nextjsVersion) {
|
|
1915
|
+
changes.push({
|
|
1916
|
+
type: "modified",
|
|
1917
|
+
label: `Next.js version: ${oldScan.nextjsVersion || "unknown"} \u2192 ${newScan.nextjsVersion || "unknown"}`
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
} else if (oldScan.framework !== newScan.framework) {
|
|
1921
|
+
if (oldScan.framework !== "unknown") {
|
|
1922
|
+
changes.push({ type: "removed", label: `${oldScan.framework} no longer detected` });
|
|
1923
|
+
}
|
|
1924
|
+
if (newScan.framework !== "unknown") {
|
|
1925
|
+
changes.push({ type: "added", label: `${newScan.framework} detected` });
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
if (oldScan.routerType !== newScan.routerType && newScan.framework === "nextjs") {
|
|
1929
|
+
changes.push({
|
|
1930
|
+
type: "modified",
|
|
1931
|
+
label: `Router type: ${oldScan.routerType || "unknown"} \u2192 ${newScan.routerType || "unknown"}`
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
if (!oldScan.typescript && newScan.typescript) {
|
|
1935
|
+
changes.push({ type: "added", label: "TypeScript detected" });
|
|
1936
|
+
} else if (oldScan.typescript && !newScan.typescript) {
|
|
1937
|
+
changes.push({ type: "removed", label: "TypeScript no longer detected" });
|
|
1938
|
+
}
|
|
1939
|
+
const oldStyles = new Set(oldScan.styling);
|
|
1940
|
+
const newStyles = new Set(newScan.styling);
|
|
1941
|
+
for (const style of newStyles) {
|
|
1942
|
+
if (!oldStyles.has(style)) {
|
|
1943
|
+
changes.push({ type: "added", label: `${style} detected (styling)` });
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
for (const style of oldStyles) {
|
|
1947
|
+
if (!newStyles.has(style)) {
|
|
1948
|
+
changes.push({ type: "removed", label: `${style} no longer detected` });
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
if (oldScan.styling.includes("tailwind") && newScan.styling.includes("tailwind") && oldScan.tailwindVersion !== newScan.tailwindVersion) {
|
|
1952
|
+
changes.push({
|
|
1953
|
+
type: "modified",
|
|
1954
|
+
label: `Tailwind version: ${oldScan.tailwindVersion || "unknown"} \u2192 ${newScan.tailwindVersion || "unknown"}`
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
if (!oldScan.monorepo && newScan.monorepo) {
|
|
1958
|
+
changes.push({
|
|
1959
|
+
type: "added",
|
|
1960
|
+
label: `monorepo detected${newScan.monorepoTool ? ` (${newScan.monorepoTool})` : ""}`
|
|
1961
|
+
});
|
|
1962
|
+
} else if (oldScan.monorepo && !newScan.monorepo) {
|
|
1963
|
+
changes.push({ type: "removed", label: "monorepo no longer detected" });
|
|
1964
|
+
} else if (oldScan.monorepoTool !== newScan.monorepoTool && newScan.monorepo) {
|
|
1965
|
+
changes.push({
|
|
1966
|
+
type: "modified",
|
|
1967
|
+
label: `monorepo tool: ${oldScan.monorepoTool || "unknown"} \u2192 ${newScan.monorepoTool || "unknown"}`
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
if (oldScan.cms !== newScan.cms) {
|
|
1971
|
+
if (oldScan.cms !== "none") {
|
|
1972
|
+
changes.push({ type: "removed", label: `${oldScan.cms} no longer detected` });
|
|
1973
|
+
}
|
|
1974
|
+
if (newScan.cms !== "none") {
|
|
1975
|
+
changes.push({ type: "added", label: `${newScan.cms} detected (CMS)` });
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
if (oldScan.packageManager !== newScan.packageManager) {
|
|
1979
|
+
changes.push({
|
|
1980
|
+
type: "modified",
|
|
1981
|
+
label: `Package manager: ${oldScan.packageManager} \u2192 ${newScan.packageManager}`
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1984
|
+
if (!oldScan.figma?.detected && newScan.figma?.detected) {
|
|
1985
|
+
changes.push({ type: "added", label: "Figma integration detected" });
|
|
1986
|
+
} else if (oldScan.figma?.detected && !newScan.figma?.detected) {
|
|
1987
|
+
changes.push({ type: "removed", label: "Figma integration no longer detected" });
|
|
1988
|
+
}
|
|
1989
|
+
if (oldScan.tools && newScan.tools) {
|
|
1990
|
+
const toolNames = Object.keys(newScan.tools);
|
|
1991
|
+
for (const tool of toolNames) {
|
|
1992
|
+
if (!oldScan.tools[tool] && newScan.tools[tool]) {
|
|
1993
|
+
changes.push({ type: "added", label: `${tool} detected (tooling)` });
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
const oldToolNames = Object.keys(oldScan.tools);
|
|
1997
|
+
for (const tool of oldToolNames) {
|
|
1998
|
+
if (oldScan.tools[tool] && !newScan.tools[tool]) {
|
|
1999
|
+
changes.push({ type: "removed", label: `${tool} no longer detected` });
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return changes;
|
|
2004
|
+
}
|
|
2005
|
+
function diffGeneratedFile(projectDir, filename, config, generate2, oldFragments, newFragments) {
|
|
2006
|
+
const filePath = path20.join(projectDir, filename);
|
|
2007
|
+
const currentContent = readFileSafe(filePath);
|
|
2008
|
+
const newContent = generate2();
|
|
2009
|
+
if (!currentContent) {
|
|
2010
|
+
if (config.templates.includes(filename)) {
|
|
2011
|
+
return { filename, status: "added", detail: "file missing, would be created" };
|
|
2012
|
+
}
|
|
2013
|
+
return { filename, status: "unchanged", detail: "not configured" };
|
|
2014
|
+
}
|
|
2015
|
+
if (currentContent.trim() === newContent.trim()) {
|
|
2016
|
+
return { filename, status: "unchanged" };
|
|
2017
|
+
}
|
|
2018
|
+
const addedFragments = newFragments.filter((f) => !oldFragments.includes(f));
|
|
2019
|
+
const removedFragments = oldFragments.filter((f) => !newFragments.includes(f));
|
|
2020
|
+
const fragmentDetails = [];
|
|
2021
|
+
if (addedFragments.length > 0) {
|
|
2022
|
+
fragmentDetails.push(`+${addedFragments.join(", +")}`);
|
|
2023
|
+
}
|
|
2024
|
+
if (removedFragments.length > 0) {
|
|
2025
|
+
fragmentDetails.push(`-${removedFragments.join(", -")}`);
|
|
2026
|
+
}
|
|
2027
|
+
const detail = fragmentDetails.length > 0 ? `template fragments changed: ${fragmentDetails.join(", ")}` : "content changed";
|
|
2028
|
+
return { filename, status: "modified", detail };
|
|
2029
|
+
}
|
|
2030
|
+
function logFileChange(result) {
|
|
2031
|
+
if (result.status === "added") {
|
|
2032
|
+
console.log(chalk4.green(` added ${result.filename}`) + (result.detail ? chalk4.dim(` (${result.detail})`) : ""));
|
|
2033
|
+
} else if (result.status === "modified") {
|
|
2034
|
+
console.log(
|
|
2035
|
+
chalk4.yellow(` modified ${result.filename}`) + (result.detail ? chalk4.dim(` (${result.detail})`) : "")
|
|
2036
|
+
);
|
|
2037
|
+
} else {
|
|
2038
|
+
console.log(chalk4.dim(` unchanged ${result.filename}`));
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
function countFilesInDir(dirPath) {
|
|
2042
|
+
try {
|
|
2043
|
+
if (!fs10.existsSync(dirPath)) return 0;
|
|
2044
|
+
const entries = fs10.readdirSync(dirPath);
|
|
2045
|
+
return entries.filter((e) => !e.startsWith(".")).length;
|
|
2046
|
+
} catch {
|
|
2047
|
+
return 0;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// src/commands/export.ts
|
|
2052
|
+
import path21 from "path";
|
|
2053
|
+
import fs11 from "fs-extra";
|
|
2054
|
+
import ora6 from "ora";
|
|
2055
|
+
import { select as select2 } from "@inquirer/prompts";
|
|
2056
|
+
var EXPORT_TARGETS = {
|
|
2057
|
+
windsurf: { file: ".windsurfrules", label: "Windsurf" },
|
|
2058
|
+
aider: { file: ".aider.conf.yml", label: "Aider" },
|
|
2059
|
+
cline: { file: ".clinerules", label: "Cline" }
|
|
2060
|
+
};
|
|
2061
|
+
function stripMarkers(content) {
|
|
2062
|
+
return content.replace("<!-- AI-KIT:START -->\n", "").replace("\n<!-- AI-KIT:END -->", "").replace(/<!-- Generated by ai-kit v[\d.]+ -->\n?/, "");
|
|
2063
|
+
}
|
|
2064
|
+
function toWindsurf(content) {
|
|
2065
|
+
const stripped = stripMarkers(content);
|
|
2066
|
+
return `# Windsurf Rules
|
|
2067
|
+
# Exported from ai-kit CLAUDE.md
|
|
2068
|
+
|
|
2069
|
+
${stripped}`;
|
|
2070
|
+
}
|
|
2071
|
+
function toAider(content) {
|
|
2072
|
+
const stripped = stripMarkers(content);
|
|
2073
|
+
const indented = stripped.split("\n").map((line) => line.length > 0 ? ` ${line}` : "").join("\n");
|
|
2074
|
+
return `# Aider configuration
|
|
2075
|
+
# Exported from ai-kit CLAUDE.md
|
|
2076
|
+
|
|
2077
|
+
conventions: |
|
|
2078
|
+
${indented}
|
|
2079
|
+
`;
|
|
2080
|
+
}
|
|
2081
|
+
function toCline(content) {
|
|
2082
|
+
const stripped = stripMarkers(content);
|
|
2083
|
+
return `# Cline Rules
|
|
2084
|
+
# Exported from ai-kit CLAUDE.md
|
|
2085
|
+
|
|
2086
|
+
${stripped}`;
|
|
2087
|
+
}
|
|
2088
|
+
async function exportCommand(targetPath, options) {
|
|
2089
|
+
const projectDir = path21.resolve(targetPath || process.cwd());
|
|
2090
|
+
const configPath = path21.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2091
|
+
const claudeMdPath = path21.join(projectDir, GENERATED_FILES.claudeMd);
|
|
2092
|
+
logSection("AI Kit \u2014 Export");
|
|
2093
|
+
if (!fileExists(configPath) && !fileExists(claudeMdPath)) {
|
|
2094
|
+
logError("No ai-kit.config.json or CLAUDE.md found. Run `ai-kit init` first.");
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const claudeContent = readFileSafe(claudeMdPath);
|
|
2098
|
+
if (!claudeContent) {
|
|
2099
|
+
logError("Could not read CLAUDE.md. Run `ai-kit init` or `ai-kit update` first.");
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
let format;
|
|
2103
|
+
if (options?.format) {
|
|
2104
|
+
const f = options.format.toLowerCase();
|
|
2105
|
+
if (f === "windsurf" || f === "aider" || f === "cline" || f === "all") {
|
|
2106
|
+
format = f;
|
|
2107
|
+
} else {
|
|
2108
|
+
logError(`Unknown format "${options.format}". Use: windsurf, aider, cline, or all.`);
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
} else {
|
|
2112
|
+
format = await select2({
|
|
2113
|
+
message: "Which format do you want to export to?",
|
|
2114
|
+
choices: [
|
|
2115
|
+
{ name: "Windsurf (.windsurfrules)", value: "windsurf" },
|
|
2116
|
+
{ name: "Aider (.aider.conf.yml)", value: "aider" },
|
|
2117
|
+
{ name: "Cline (.clinerules)", value: "cline" },
|
|
2118
|
+
{ name: "All of the above", value: "all" }
|
|
2119
|
+
]
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
2122
|
+
const formats = format === "all" ? ["windsurf", "aider", "cline"] : [format];
|
|
2123
|
+
const spinner = ora6("Exporting rules to other AI tools...").start();
|
|
2124
|
+
const transformers = {
|
|
2125
|
+
windsurf: toWindsurf,
|
|
2126
|
+
aider: toAider,
|
|
2127
|
+
cline: toCline
|
|
2128
|
+
};
|
|
2129
|
+
let exported = 0;
|
|
2130
|
+
for (const fmt2 of formats) {
|
|
2131
|
+
const target = EXPORT_TARGETS[fmt2];
|
|
2132
|
+
const transformer = transformers[fmt2];
|
|
2133
|
+
const outputPath = path21.join(projectDir, target.file);
|
|
2134
|
+
const transformed = transformer(claudeContent);
|
|
2135
|
+
await fs11.writeFile(outputPath, transformed, "utf-8");
|
|
2136
|
+
exported++;
|
|
2137
|
+
}
|
|
2138
|
+
spinner.succeed("Export complete");
|
|
2139
|
+
console.log("");
|
|
2140
|
+
for (const fmt2 of formats) {
|
|
2141
|
+
const target = EXPORT_TARGETS[fmt2];
|
|
2142
|
+
logSuccess(`${target.file} generated (${target.label})`);
|
|
2143
|
+
}
|
|
2144
|
+
console.log("");
|
|
2145
|
+
logInfo(
|
|
2146
|
+
`${exported} file${exported !== 1 ? "s" : ""} exported. These contain the same rules as your CLAUDE.md.`
|
|
2147
|
+
);
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// src/commands/stats.ts
|
|
2151
|
+
import path22 from "path";
|
|
2152
|
+
import chalk5 from "chalk";
|
|
2153
|
+
var SKILL_CATEGORIES = {
|
|
2154
|
+
"Getting Started": ["prompt-help", "understand"],
|
|
2155
|
+
"Building": [
|
|
2156
|
+
"new-component",
|
|
2157
|
+
"new-page",
|
|
2158
|
+
"api-route",
|
|
2159
|
+
"error-boundary",
|
|
2160
|
+
"extract-hook",
|
|
2161
|
+
"figma-to-code",
|
|
2162
|
+
"design-tokens"
|
|
2163
|
+
],
|
|
2164
|
+
"Quality & Review": [
|
|
2165
|
+
"review",
|
|
2166
|
+
"test",
|
|
2167
|
+
"accessibility-audit",
|
|
2168
|
+
"security-check",
|
|
2169
|
+
"responsive-check",
|
|
2170
|
+
"type-fix",
|
|
2171
|
+
"pre-pr"
|
|
2172
|
+
],
|
|
2173
|
+
"Maintenance": [
|
|
2174
|
+
"optimize",
|
|
2175
|
+
"refactor",
|
|
2176
|
+
"dep-check",
|
|
2177
|
+
"bundle-check",
|
|
2178
|
+
"migrate",
|
|
2179
|
+
"env-setup"
|
|
2180
|
+
],
|
|
2181
|
+
"Workflow": ["fix-bug", "commit-msg", "document", "token-tips"]
|
|
2182
|
+
};
|
|
2183
|
+
function getComplexityLabel(score) {
|
|
2184
|
+
if (score >= 10) return "Enterprise";
|
|
2185
|
+
if (score >= 7) return "Complex";
|
|
2186
|
+
if (score >= 4) return "Moderate";
|
|
2187
|
+
return "Simple";
|
|
2188
|
+
}
|
|
2189
|
+
function calculateComplexity(config) {
|
|
2190
|
+
const scan = config.scanResult;
|
|
2191
|
+
let score = 0;
|
|
2192
|
+
const items = [];
|
|
2193
|
+
const hasFramework = scan.framework !== "unknown";
|
|
2194
|
+
if (hasFramework) score += 1;
|
|
2195
|
+
const frameworkLabel = scan.framework === "nextjs" ? `Next.js ${scan.nextjsVersion || ""} (${scan.routerType === "app" ? "App Router" : scan.routerType === "pages" ? "Pages Router" : "Hybrid"})` : scan.framework === "react" ? "React" : "Unknown";
|
|
2196
|
+
items.push({ label: frameworkLabel.trim(), active: hasFramework });
|
|
2197
|
+
const hasCms = scan.cms !== "none";
|
|
2198
|
+
if (hasCms) score += 2;
|
|
2199
|
+
const cmsLabel = scan.cms === "sitecore-xmc" ? "Sitecore XM Cloud" : scan.cms === "sitecore-jss" ? "Sitecore JSS" : "CMS";
|
|
2200
|
+
items.push({ label: cmsLabel, active: hasCms });
|
|
2201
|
+
if (scan.typescript) score += 1;
|
|
2202
|
+
items.push({
|
|
2203
|
+
label: `TypeScript${scan.typescriptStrict ? " (strict)" : ""}`,
|
|
2204
|
+
active: scan.typescript
|
|
2205
|
+
});
|
|
2206
|
+
for (const style of scan.styling) {
|
|
2207
|
+
score += 1;
|
|
2208
|
+
const styleLabel = style === "tailwind" ? `Tailwind CSS${scan.tailwindVersion ? ` v${scan.tailwindVersion}` : ""}` : style === "css-modules" ? "CSS Modules" : style === "styled-components" ? "Styled Components" : style === "scss" ? "SCSS" : style;
|
|
2209
|
+
items.push({ label: styleLabel, active: true });
|
|
2210
|
+
}
|
|
2211
|
+
items.push({
|
|
2212
|
+
label: scan.monorepo ? `Monorepo (${scan.monorepoTool || "detected"})` : "Monorepo",
|
|
2213
|
+
active: scan.monorepo
|
|
2214
|
+
});
|
|
2215
|
+
if (scan.monorepo) score += 2;
|
|
2216
|
+
items.push({ label: "Figma", active: scan.figma.detected });
|
|
2217
|
+
if (scan.figma.detected) score += 1;
|
|
2218
|
+
return { score, items };
|
|
2219
|
+
}
|
|
2220
|
+
function categorizeSkills(skills) {
|
|
2221
|
+
const result = {};
|
|
2222
|
+
const categorized = /* @__PURE__ */ new Set();
|
|
2223
|
+
for (const [category, categorySkills] of Object.entries(SKILL_CATEGORIES)) {
|
|
2224
|
+
const matched = skills.filter((s) => categorySkills.includes(s));
|
|
2225
|
+
if (matched.length > 0) {
|
|
2226
|
+
result[category] = matched;
|
|
2227
|
+
matched.forEach((s) => categorized.add(s));
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
const uncategorized = skills.filter((s) => !categorized.has(s));
|
|
2231
|
+
if (uncategorized.length > 0) {
|
|
2232
|
+
result["Other"] = uncategorized;
|
|
2233
|
+
}
|
|
2234
|
+
return result;
|
|
2235
|
+
}
|
|
2236
|
+
var TOOL_DISPLAY_NAMES = {
|
|
2237
|
+
playwright: "Playwright",
|
|
2238
|
+
storybook: "Storybook",
|
|
2239
|
+
eslint: "ESLint",
|
|
2240
|
+
prettier: "Prettier",
|
|
2241
|
+
axeCore: "axe-core",
|
|
2242
|
+
snyk: "Snyk",
|
|
2243
|
+
knip: "Knip",
|
|
2244
|
+
bundleAnalyzer: "Bundle Analyzer"
|
|
2245
|
+
};
|
|
2246
|
+
var MCP_DISPLAY_NAMES = {
|
|
2247
|
+
playwright: "Playwright",
|
|
2248
|
+
figma: "Figma",
|
|
2249
|
+
github: "GitHub",
|
|
2250
|
+
context7: "Context7",
|
|
2251
|
+
perplexity: "Perplexity"
|
|
2252
|
+
};
|
|
2253
|
+
async function statsCommand(targetPath) {
|
|
2254
|
+
const projectDir = path22.resolve(targetPath || process.cwd());
|
|
2255
|
+
const configPath = path22.join(projectDir, AI_KIT_CONFIG_FILE);
|
|
2256
|
+
logSection("AI Kit \u2014 Project Stats");
|
|
2257
|
+
console.log("");
|
|
2258
|
+
if (!fileExists(configPath)) {
|
|
2259
|
+
logWarning(
|
|
2260
|
+
"ai-kit.config.json not found. Run `ai-kit init` first."
|
|
2261
|
+
);
|
|
2262
|
+
return;
|
|
2263
|
+
}
|
|
2264
|
+
const config = readJsonSafe(configPath);
|
|
2265
|
+
if (!config) {
|
|
2266
|
+
logWarning(
|
|
2267
|
+
"ai-kit.config.json is corrupted or unreadable. Run `ai-kit init` to re-initialize."
|
|
2268
|
+
);
|
|
2269
|
+
return;
|
|
2270
|
+
}
|
|
2271
|
+
const scan = config.scanResult;
|
|
2272
|
+
console.log(` Project: ${chalk5.bold(scan.projectName)}`);
|
|
2273
|
+
console.log(` Version: ai-kit v${config.version}`);
|
|
2274
|
+
console.log(` Generated: ${config.generatedAt}`);
|
|
2275
|
+
console.log(` Package Manager: ${scan.packageManager}`);
|
|
2276
|
+
console.log("");
|
|
2277
|
+
const { score, items } = calculateComplexity(config);
|
|
2278
|
+
const label = getComplexityLabel(score);
|
|
2279
|
+
const maxScore = 10;
|
|
2280
|
+
console.log(
|
|
2281
|
+
` ${chalk5.bold("Stack Complexity:")} ${score}/${maxScore} (${label})`
|
|
2282
|
+
);
|
|
2283
|
+
for (const item of items) {
|
|
2284
|
+
if (item.active) {
|
|
2285
|
+
console.log(` ${chalk5.green("\u2713")} ${item.label}`);
|
|
2286
|
+
} else {
|
|
2287
|
+
console.log(` ${chalk5.gray("\u2717")} ${chalk5.gray(item.label)}`);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
console.log("");
|
|
2291
|
+
const activeFragments = (config.templates || []).filter(
|
|
2292
|
+
(t) => TEMPLATE_FRAGMENTS.includes(t)
|
|
2293
|
+
);
|
|
2294
|
+
const fragmentCount = activeFragments.length;
|
|
2295
|
+
console.log(
|
|
2296
|
+
` ${chalk5.bold("Active Rules:")} ${fragmentCount} template fragment${fragmentCount !== 1 ? "s" : ""}`
|
|
2297
|
+
);
|
|
2298
|
+
if (fragmentCount > 0) {
|
|
2299
|
+
console.log(` ${activeFragments.join(", ")}`);
|
|
2300
|
+
}
|
|
2301
|
+
console.log("");
|
|
2302
|
+
const skills = config.commands || [];
|
|
2303
|
+
const totalSkills = skills.length;
|
|
2304
|
+
const categorized = categorizeSkills(skills);
|
|
2305
|
+
console.log(` ${chalk5.bold("Skills:")} ${totalSkills} total`);
|
|
2306
|
+
for (const [category, categorySkills] of Object.entries(categorized)) {
|
|
2307
|
+
console.log(` ${category}: ${categorySkills.length}`);
|
|
2308
|
+
}
|
|
2309
|
+
console.log("");
|
|
2310
|
+
console.log(` ${chalk5.bold("Tools:")}`);
|
|
2311
|
+
const toolEntries = Object.entries(scan.tools);
|
|
2312
|
+
const detectedTools = [];
|
|
2313
|
+
const missingTools = [];
|
|
2314
|
+
for (const [key, detected] of toolEntries) {
|
|
2315
|
+
const displayName = TOOL_DISPLAY_NAMES[key] || key;
|
|
2316
|
+
if (detected) {
|
|
2317
|
+
detectedTools.push(`${chalk5.green("\u2713")} ${displayName}`);
|
|
2318
|
+
} else {
|
|
2319
|
+
missingTools.push(`${chalk5.yellow("\u2717")} ${displayName}`);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
const allToolItems = [...detectedTools, ...missingTools];
|
|
2323
|
+
for (let i = 0; i < allToolItems.length; i += 3) {
|
|
2324
|
+
const row = allToolItems.slice(i, i + 3).map((item) => item.padEnd(22)).join("");
|
|
2325
|
+
console.log(` ${row}`);
|
|
2326
|
+
}
|
|
2327
|
+
console.log("");
|
|
2328
|
+
console.log(` ${chalk5.bold("MCP Servers:")}`);
|
|
2329
|
+
const mcpEntries = Object.entries(scan.mcpServers);
|
|
2330
|
+
const configuredMcps = [];
|
|
2331
|
+
const missingMcps = [];
|
|
2332
|
+
for (const [key, configured] of mcpEntries) {
|
|
2333
|
+
const displayName = MCP_DISPLAY_NAMES[key] || key;
|
|
2334
|
+
if (configured) {
|
|
2335
|
+
configuredMcps.push(`${chalk5.green("\u2713")} ${displayName}`);
|
|
2336
|
+
} else {
|
|
2337
|
+
missingMcps.push(`${chalk5.yellow("\u2717")} ${displayName}`);
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
const allMcpItems = [...configuredMcps, ...missingMcps];
|
|
2341
|
+
for (let i = 0; i < allMcpItems.length; i += 3) {
|
|
2342
|
+
const row = allMcpItems.slice(i, i + 3).map((item) => item.padEnd(22)).join("");
|
|
2343
|
+
console.log(` ${row}`);
|
|
2344
|
+
}
|
|
2345
|
+
console.log("");
|
|
2346
|
+
}
|
|
2347
|
+
|
|
1375
2348
|
// src/index.ts
|
|
1376
2349
|
var program = new Command();
|
|
1377
2350
|
program.name("ai-kit").description(
|
|
@@ -1410,7 +2383,7 @@ program.command("reset").description("Remove all AI Kit generated files").argume
|
|
|
1410
2383
|
process.exit(1);
|
|
1411
2384
|
}
|
|
1412
2385
|
});
|
|
1413
|
-
program.command("tokens").description("Show token usage summary and cost estimates").option("--export", "Export data and open HTML dashboard").action(async (opts) => {
|
|
2386
|
+
program.command("tokens").description("Show token usage summary and cost estimates").option("--export", "Export data and open HTML dashboard").option("--csv", "Export daily usage to CSV file").option("--budget <amount>", "Monthly budget in USD (default: $20)", parseFloat).action(async (opts) => {
|
|
1414
2387
|
try {
|
|
1415
2388
|
await tokensCommand(opts);
|
|
1416
2389
|
} catch (err) {
|
|
@@ -1421,5 +2394,49 @@ program.command("tokens").description("Show token usage summary and cost estimat
|
|
|
1421
2394
|
process.exit(1);
|
|
1422
2395
|
}
|
|
1423
2396
|
});
|
|
2397
|
+
program.command("doctor").description("Diagnose AI Kit setup and check for issues").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
|
|
2398
|
+
try {
|
|
2399
|
+
await doctorCommand(targetPath);
|
|
2400
|
+
} catch (err) {
|
|
2401
|
+
if (err.name === "ExitPromptError") {
|
|
2402
|
+
process.exit(0);
|
|
2403
|
+
}
|
|
2404
|
+
console.error(err);
|
|
2405
|
+
process.exit(1);
|
|
2406
|
+
}
|
|
2407
|
+
});
|
|
2408
|
+
program.command("diff").description("Show what would change on update (dry run)").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
|
|
2409
|
+
try {
|
|
2410
|
+
await diffCommand(targetPath);
|
|
2411
|
+
} catch (err) {
|
|
2412
|
+
if (err.name === "ExitPromptError") {
|
|
2413
|
+
process.exit(0);
|
|
2414
|
+
}
|
|
2415
|
+
console.error(err);
|
|
2416
|
+
process.exit(1);
|
|
2417
|
+
}
|
|
2418
|
+
});
|
|
2419
|
+
program.command("export").description("Export rules to other AI tools (Windsurf, Aider, Cline)").argument("[path]", "Project directory (defaults to current directory)").option("--format <format>", "Export format: windsurf, aider, cline, or all").action(async (targetPath, opts) => {
|
|
2420
|
+
try {
|
|
2421
|
+
await exportCommand(targetPath, opts);
|
|
2422
|
+
} catch (err) {
|
|
2423
|
+
if (err.name === "ExitPromptError") {
|
|
2424
|
+
process.exit(0);
|
|
2425
|
+
}
|
|
2426
|
+
console.error(err);
|
|
2427
|
+
process.exit(1);
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
program.command("stats").description("Show project setup statistics and complexity").argument("[path]", "Project directory (defaults to current directory)").action(async (targetPath) => {
|
|
2431
|
+
try {
|
|
2432
|
+
await statsCommand(targetPath);
|
|
2433
|
+
} catch (err) {
|
|
2434
|
+
if (err.name === "ExitPromptError") {
|
|
2435
|
+
process.exit(0);
|
|
2436
|
+
}
|
|
2437
|
+
console.error(err);
|
|
2438
|
+
process.exit(1);
|
|
2439
|
+
}
|
|
2440
|
+
});
|
|
1424
2441
|
program.parse();
|
|
1425
2442
|
//# sourceMappingURL=index.js.map
|