erosolar-cli 1.6.21 → 1.6.22

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.
@@ -471,6 +471,1265 @@ export function createDevTools(workingDir) {
471
471
  }
472
472
  },
473
473
  },
474
+ // ========================================================================
475
+ // Git Workflow Tools
476
+ // ========================================================================
477
+ {
478
+ name: 'git_release',
479
+ description: 'Create a git release with tag, changelog generation, and optional GitHub release',
480
+ parameters: {
481
+ type: 'object',
482
+ properties: {
483
+ version: {
484
+ type: 'string',
485
+ description: 'Version tag (e.g., v1.0.0). If not provided, will auto-generate based on commits.',
486
+ },
487
+ message: {
488
+ type: 'string',
489
+ description: 'Release message/description',
490
+ },
491
+ generateChangelog: {
492
+ type: 'boolean',
493
+ description: 'Auto-generate changelog from commits since last tag (default: true)',
494
+ },
495
+ pushTag: {
496
+ type: 'boolean',
497
+ description: 'Push the tag to remote (default: true)',
498
+ },
499
+ createGithubRelease: {
500
+ type: 'boolean',
501
+ description: 'Create a GitHub release using gh CLI (default: false)',
502
+ },
503
+ },
504
+ additionalProperties: false,
505
+ },
506
+ handler: async (args) => {
507
+ const version = typeof args['version'] === 'string' ? args['version'] : undefined;
508
+ const message = typeof args['message'] === 'string' ? args['message'] : undefined;
509
+ const generateChangelog = args['generateChangelog'] !== false;
510
+ const pushTag = args['pushTag'] !== false;
511
+ const createGithubRelease = args['createGithubRelease'] === true;
512
+ const output = ['# Git Release', ''];
513
+ try {
514
+ // Get last tag
515
+ let lastTag = '';
516
+ try {
517
+ const { stdout } = await execAsync('git describe --tags --abbrev=0 2>/dev/null || echo ""', {
518
+ cwd: workingDir,
519
+ timeout: 10000,
520
+ shell: '/bin/bash',
521
+ });
522
+ lastTag = stdout.trim();
523
+ }
524
+ catch {
525
+ // No tags exist
526
+ }
527
+ // Determine version
528
+ let tagVersion = version;
529
+ if (!tagVersion) {
530
+ if (lastTag) {
531
+ // Increment patch version
532
+ const match = lastTag.match(/v?(\d+)\.(\d+)\.(\d+)/);
533
+ if (match) {
534
+ const [, major, minor, patch] = match;
535
+ tagVersion = `v${major}.${minor}.${parseInt(patch || '0', 10) + 1}`;
536
+ }
537
+ else {
538
+ tagVersion = 'v0.0.1';
539
+ }
540
+ }
541
+ else {
542
+ tagVersion = 'v0.0.1';
543
+ }
544
+ }
545
+ output.push(`Version: ${tagVersion}`);
546
+ output.push(`Previous: ${lastTag || '(none)'}`);
547
+ // Generate changelog
548
+ let changelog = '';
549
+ if (generateChangelog) {
550
+ output.push('');
551
+ output.push('## Changelog');
552
+ try {
553
+ const range = lastTag ? `${lastTag}..HEAD` : 'HEAD';
554
+ const { stdout: commits } = await execAsync(`git log ${range} --pretty=format:"- %s (%h)" --no-merges 2>/dev/null | head -50`, { cwd: workingDir, timeout: 10000, shell: '/bin/bash' });
555
+ changelog = commits.trim() || 'No commits since last release';
556
+ output.push(changelog);
557
+ }
558
+ catch {
559
+ changelog = 'Could not generate changelog';
560
+ output.push(changelog);
561
+ }
562
+ }
563
+ // Create tag
564
+ output.push('');
565
+ output.push('## Creating Tag');
566
+ const tagMessage = message || `Release ${tagVersion}\n\n${changelog}`;
567
+ try {
568
+ await execAsync(`git tag -a "${tagVersion}" -m "${tagMessage.replace(/"/g, '\\"')}"`, {
569
+ cwd: workingDir,
570
+ timeout: 30000,
571
+ });
572
+ output.push(`✓ Created tag: ${tagVersion}`);
573
+ }
574
+ catch (e) {
575
+ output.push(`✗ Failed to create tag: ${e.message}`);
576
+ return output.join('\n');
577
+ }
578
+ // Push tag
579
+ if (pushTag) {
580
+ output.push('');
581
+ output.push('## Pushing Tag');
582
+ try {
583
+ await execAsync(`git push origin "${tagVersion}"`, { cwd: workingDir, timeout: 60000 });
584
+ output.push(`✓ Pushed tag to origin`);
585
+ }
586
+ catch (e) {
587
+ output.push(`✗ Failed to push tag: ${e.message}`);
588
+ }
589
+ }
590
+ // Create GitHub release
591
+ if (createGithubRelease) {
592
+ output.push('');
593
+ output.push('## Creating GitHub Release');
594
+ try {
595
+ const releaseNotes = changelog || message || `Release ${tagVersion}`;
596
+ await execAsync(`gh release create "${tagVersion}" --title "${tagVersion}" --notes "${releaseNotes.replace(/"/g, '\\"')}"`, { cwd: workingDir, timeout: 60000 });
597
+ output.push(`✓ Created GitHub release`);
598
+ }
599
+ catch (e) {
600
+ output.push(`✗ Failed to create GitHub release: ${e.message}`);
601
+ output.push('Make sure gh CLI is installed and authenticated');
602
+ }
603
+ }
604
+ return output.join('\n');
605
+ }
606
+ catch (error) {
607
+ return `Error creating release: ${error.message}`;
608
+ }
609
+ },
610
+ },
611
+ {
612
+ name: 'git_sync',
613
+ description: 'Sync local branch with remote - fetch, pull, and optionally push',
614
+ parameters: {
615
+ type: 'object',
616
+ properties: {
617
+ branch: {
618
+ type: 'string',
619
+ description: 'Branch to sync (default: current branch)',
620
+ },
621
+ push: {
622
+ type: 'boolean',
623
+ description: 'Push local commits after pulling (default: false)',
624
+ },
625
+ rebase: {
626
+ type: 'boolean',
627
+ description: 'Use rebase instead of merge when pulling (default: false)',
628
+ },
629
+ stashChanges: {
630
+ type: 'boolean',
631
+ description: 'Stash uncommitted changes before sync, restore after (default: true)',
632
+ },
633
+ },
634
+ additionalProperties: false,
635
+ },
636
+ handler: async (args) => {
637
+ const branch = typeof args['branch'] === 'string' ? args['branch'] : undefined;
638
+ const push = args['push'] === true;
639
+ const rebase = args['rebase'] === true;
640
+ const stashChanges = args['stashChanges'] !== false;
641
+ const output = ['# Git Sync', ''];
642
+ let stashed = false;
643
+ try {
644
+ // Get current branch if not specified
645
+ let targetBranch = branch;
646
+ if (!targetBranch) {
647
+ const { stdout } = await execAsync('git branch --show-current', { cwd: workingDir, timeout: 10000 });
648
+ targetBranch = stdout.trim();
649
+ }
650
+ output.push(`Branch: ${targetBranch}`);
651
+ // Check for uncommitted changes
652
+ const { stdout: status } = await execAsync('git status --porcelain', { cwd: workingDir, timeout: 10000 });
653
+ const hasChanges = status.trim().length > 0;
654
+ if (hasChanges && stashChanges) {
655
+ output.push('');
656
+ output.push('## Stashing Changes');
657
+ await execAsync('git stash push -m "git_sync auto-stash"', { cwd: workingDir, timeout: 30000 });
658
+ output.push('✓ Stashed uncommitted changes');
659
+ stashed = true;
660
+ }
661
+ else if (hasChanges) {
662
+ output.push('');
663
+ output.push('⚠ Warning: Uncommitted changes present, sync may fail');
664
+ }
665
+ // Fetch
666
+ output.push('');
667
+ output.push('## Fetching');
668
+ await execAsync('git fetch --all --prune', { cwd: workingDir, timeout: 60000 });
669
+ output.push('✓ Fetched from all remotes');
670
+ // Pull
671
+ output.push('');
672
+ output.push('## Pulling');
673
+ const pullCmd = rebase ? `git pull --rebase origin ${targetBranch}` : `git pull origin ${targetBranch}`;
674
+ try {
675
+ const { stdout: pullOut } = await execAsync(pullCmd, { cwd: workingDir, timeout: 120000 });
676
+ output.push(`✓ Pulled from origin/${targetBranch}`);
677
+ if (pullOut.includes('Already up to date')) {
678
+ output.push('Already up to date');
679
+ }
680
+ }
681
+ catch (e) {
682
+ output.push(`✗ Pull failed: ${e.message}`);
683
+ if (stashed) {
684
+ await execAsync('git stash pop', { cwd: workingDir, timeout: 30000 });
685
+ output.push('✓ Restored stashed changes');
686
+ }
687
+ return output.join('\n');
688
+ }
689
+ // Push
690
+ if (push) {
691
+ output.push('');
692
+ output.push('## Pushing');
693
+ try {
694
+ await execAsync(`git push origin ${targetBranch}`, { cwd: workingDir, timeout: 60000 });
695
+ output.push(`✓ Pushed to origin/${targetBranch}`);
696
+ }
697
+ catch (e) {
698
+ output.push(`✗ Push failed: ${e.message}`);
699
+ }
700
+ }
701
+ // Restore stash
702
+ if (stashed) {
703
+ output.push('');
704
+ output.push('## Restoring Changes');
705
+ try {
706
+ await execAsync('git stash pop', { cwd: workingDir, timeout: 30000 });
707
+ output.push('✓ Restored stashed changes');
708
+ }
709
+ catch (e) {
710
+ output.push(`⚠ Failed to restore stash: ${e.message}`);
711
+ output.push('Run `git stash pop` manually');
712
+ }
713
+ }
714
+ return output.join('\n');
715
+ }
716
+ catch (error) {
717
+ if (stashed) {
718
+ try {
719
+ await execAsync('git stash pop', { cwd: workingDir, timeout: 30000 });
720
+ }
721
+ catch {
722
+ // Ignore
723
+ }
724
+ }
725
+ return `Error syncing: ${error.message}`;
726
+ }
727
+ },
728
+ },
729
+ {
730
+ name: 'git_cleanup',
731
+ description: 'Clean up git repository - prune branches, garbage collect, remove untracked files',
732
+ parameters: {
733
+ type: 'object',
734
+ properties: {
735
+ pruneBranches: {
736
+ type: 'boolean',
737
+ description: 'Delete local branches that have been merged (default: true)',
738
+ },
739
+ pruneRemote: {
740
+ type: 'boolean',
741
+ description: 'Prune remote-tracking branches (default: true)',
742
+ },
743
+ gc: {
744
+ type: 'boolean',
745
+ description: 'Run garbage collection (default: true)',
746
+ },
747
+ cleanUntracked: {
748
+ type: 'boolean',
749
+ description: 'Remove untracked files (DANGEROUS - default: false)',
750
+ },
751
+ dryRun: {
752
+ type: 'boolean',
753
+ description: 'Show what would be done without making changes',
754
+ },
755
+ },
756
+ additionalProperties: false,
757
+ },
758
+ handler: async (args) => {
759
+ const pruneBranches = args['pruneBranches'] !== false;
760
+ const pruneRemote = args['pruneRemote'] !== false;
761
+ const gc = args['gc'] !== false;
762
+ const cleanUntracked = args['cleanUntracked'] === true;
763
+ const dryRun = args['dryRun'] === true;
764
+ const output = ['# Git Cleanup', ''];
765
+ if (dryRun)
766
+ output.push('**DRY RUN - no changes will be made**\n');
767
+ try {
768
+ // Prune remote-tracking branches
769
+ if (pruneRemote) {
770
+ output.push('## Remote-tracking Branches');
771
+ if (dryRun) {
772
+ const { stdout } = await execAsync('git remote prune origin --dry-run', {
773
+ cwd: workingDir,
774
+ timeout: 30000,
775
+ });
776
+ output.push(stdout.trim() || 'No stale branches to prune');
777
+ }
778
+ else {
779
+ const { stdout } = await execAsync('git remote prune origin', { cwd: workingDir, timeout: 30000 });
780
+ output.push(stdout.trim() || '✓ No stale branches to prune');
781
+ }
782
+ output.push('');
783
+ }
784
+ // Find and delete merged branches
785
+ if (pruneBranches) {
786
+ output.push('## Merged Local Branches');
787
+ const { stdout: currentBranch } = await execAsync('git branch --show-current', {
788
+ cwd: workingDir,
789
+ timeout: 10000,
790
+ });
791
+ const { stdout: mergedBranches } = await execAsync('git branch --merged | grep -v "\\*" | grep -v "main" | grep -v "master" | grep -v "develop"', { cwd: workingDir, timeout: 10000, shell: '/bin/bash' }).catch(() => ({ stdout: '' }));
792
+ const branches = mergedBranches
793
+ .split('\n')
794
+ .map((b) => b.trim())
795
+ .filter((b) => b && b !== currentBranch.trim());
796
+ if (branches.length === 0) {
797
+ output.push('No merged branches to delete');
798
+ }
799
+ else {
800
+ for (const branch of branches) {
801
+ if (dryRun) {
802
+ output.push(`Would delete: ${branch}`);
803
+ }
804
+ else {
805
+ try {
806
+ await execAsync(`git branch -d "${branch}"`, { cwd: workingDir, timeout: 10000 });
807
+ output.push(`✓ Deleted: ${branch}`);
808
+ }
809
+ catch {
810
+ output.push(`✗ Could not delete: ${branch}`);
811
+ }
812
+ }
813
+ }
814
+ }
815
+ output.push('');
816
+ }
817
+ // Clean untracked files
818
+ if (cleanUntracked) {
819
+ output.push('## Untracked Files');
820
+ if (dryRun) {
821
+ const { stdout } = await execAsync('git clean -n -d', { cwd: workingDir, timeout: 30000 });
822
+ output.push(stdout.trim() || 'No untracked files to remove');
823
+ }
824
+ else {
825
+ const { stdout } = await execAsync('git clean -f -d', { cwd: workingDir, timeout: 30000 });
826
+ output.push(stdout.trim() || '✓ No untracked files to remove');
827
+ }
828
+ output.push('');
829
+ }
830
+ // Garbage collection
831
+ if (gc && !dryRun) {
832
+ output.push('## Garbage Collection');
833
+ await execAsync('git gc --auto', { cwd: workingDir, timeout: 300000 });
834
+ output.push('✓ Garbage collection complete');
835
+ }
836
+ return output.join('\n');
837
+ }
838
+ catch (error) {
839
+ return `Error during cleanup: ${error.message}`;
840
+ }
841
+ },
842
+ },
843
+ // ========================================================================
844
+ // Docker Tools
845
+ // ========================================================================
846
+ {
847
+ name: 'docker_build',
848
+ description: 'Build a Docker image with optional tagging and pushing to registry',
849
+ parameters: {
850
+ type: 'object',
851
+ properties: {
852
+ tag: {
853
+ type: 'string',
854
+ description: 'Image tag (e.g., myapp:latest)',
855
+ },
856
+ dockerfile: {
857
+ type: 'string',
858
+ description: 'Path to Dockerfile (default: Dockerfile)',
859
+ },
860
+ context: {
861
+ type: 'string',
862
+ description: 'Build context path (default: .)',
863
+ },
864
+ push: {
865
+ type: 'boolean',
866
+ description: 'Push to registry after building (default: false)',
867
+ },
868
+ noCache: {
869
+ type: 'boolean',
870
+ description: 'Build without cache (default: false)',
871
+ },
872
+ buildArgs: {
873
+ type: 'object',
874
+ description: 'Build arguments as key-value pairs',
875
+ },
876
+ platform: {
877
+ type: 'string',
878
+ description: 'Target platform (e.g., linux/amd64,linux/arm64)',
879
+ },
880
+ },
881
+ required: ['tag'],
882
+ additionalProperties: false,
883
+ },
884
+ handler: async (args) => {
885
+ const tag = args['tag'];
886
+ const dockerfile = typeof args['dockerfile'] === 'string' ? args['dockerfile'] : 'Dockerfile';
887
+ const context = typeof args['context'] === 'string' ? args['context'] : '.';
888
+ const push = args['push'] === true;
889
+ const noCache = args['noCache'] === true;
890
+ const buildArgs = args['buildArgs'];
891
+ const platform = typeof args['platform'] === 'string' ? args['platform'] : undefined;
892
+ const output = ['# Docker Build', ''];
893
+ try {
894
+ // Build command
895
+ let cmd = `docker build -t "${tag}" -f "${dockerfile}"`;
896
+ if (noCache)
897
+ cmd += ' --no-cache';
898
+ if (platform)
899
+ cmd += ` --platform ${platform}`;
900
+ if (buildArgs) {
901
+ for (const [key, value] of Object.entries(buildArgs)) {
902
+ cmd += ` --build-arg ${key}="${value}"`;
903
+ }
904
+ }
905
+ cmd += ` "${context}"`;
906
+ output.push(`## Building Image`);
907
+ output.push(`Command: ${cmd}`);
908
+ output.push('');
909
+ const { stderr } = await execAsync(cmd, {
910
+ cwd: workingDir,
911
+ timeout: 600000, // 10 minutes
912
+ maxBuffer: 1024 * 1024 * 50,
913
+ });
914
+ output.push('✓ Build completed');
915
+ if (stderr && !stderr.includes('deprecated')) {
916
+ output.push(`Warnings: ${stderr.substring(0, 500)}`);
917
+ }
918
+ // Push if requested
919
+ if (push) {
920
+ output.push('');
921
+ output.push('## Pushing Image');
922
+ try {
923
+ await execAsync(`docker push "${tag}"`, { cwd: workingDir, timeout: 300000 });
924
+ output.push(`✓ Pushed ${tag}`);
925
+ }
926
+ catch (e) {
927
+ output.push(`✗ Push failed: ${e.message}`);
928
+ }
929
+ }
930
+ return output.join('\n');
931
+ }
932
+ catch (error) {
933
+ return `Docker build failed: ${error.message}\n${error.stderr || ''}`;
934
+ }
935
+ },
936
+ },
937
+ {
938
+ name: 'docker_compose',
939
+ description: 'Run docker-compose commands (up, down, restart, logs, etc.)',
940
+ parameters: {
941
+ type: 'object',
942
+ properties: {
943
+ action: {
944
+ type: 'string',
945
+ enum: ['up', 'down', 'restart', 'logs', 'ps', 'build', 'pull', 'stop', 'start'],
946
+ description: 'Docker compose action',
947
+ },
948
+ services: {
949
+ type: 'array',
950
+ items: { type: 'string' },
951
+ description: 'Specific services to target (default: all)',
952
+ },
953
+ detach: {
954
+ type: 'boolean',
955
+ description: 'Run in detached mode (for up/restart)',
956
+ },
957
+ build: {
958
+ type: 'boolean',
959
+ description: 'Build images before starting (for up)',
960
+ },
961
+ follow: {
962
+ type: 'boolean',
963
+ description: 'Follow log output (for logs)',
964
+ },
965
+ tail: {
966
+ type: 'number',
967
+ description: 'Number of lines to show from end of logs',
968
+ },
969
+ composeFile: {
970
+ type: 'string',
971
+ description: 'Path to compose file (default: docker-compose.yml)',
972
+ },
973
+ },
974
+ required: ['action'],
975
+ additionalProperties: false,
976
+ },
977
+ handler: async (args) => {
978
+ const action = args['action'];
979
+ const services = Array.isArray(args['services']) ? args['services'] : [];
980
+ const detach = args['detach'] === true;
981
+ const build = args['build'] === true;
982
+ const follow = args['follow'] === true;
983
+ const tail = typeof args['tail'] === 'number' ? args['tail'] : undefined;
984
+ const composeFile = typeof args['composeFile'] === 'string' ? args['composeFile'] : undefined;
985
+ const output = ['# Docker Compose', ''];
986
+ try {
987
+ // Build command
988
+ let cmd = 'docker compose';
989
+ if (composeFile)
990
+ cmd += ` -f "${composeFile}"`;
991
+ cmd += ` ${action}`;
992
+ // Action-specific options
993
+ if (action === 'up') {
994
+ if (detach)
995
+ cmd += ' -d';
996
+ if (build)
997
+ cmd += ' --build';
998
+ }
999
+ if (action === 'logs') {
1000
+ if (follow)
1001
+ cmd += ' -f';
1002
+ if (tail)
1003
+ cmd += ` --tail=${tail}`;
1004
+ }
1005
+ if (services.length > 0) {
1006
+ cmd += ' ' + services.join(' ');
1007
+ }
1008
+ output.push(`Command: ${cmd}`);
1009
+ output.push('');
1010
+ const timeout = action === 'logs' && follow ? 30000 : 300000;
1011
+ const { stdout, stderr } = await execAsync(cmd, {
1012
+ cwd: workingDir,
1013
+ timeout,
1014
+ maxBuffer: 1024 * 1024 * 10,
1015
+ });
1016
+ if (stdout)
1017
+ output.push(stdout.substring(0, 5000));
1018
+ if (stderr && !stderr.includes('deprecated')) {
1019
+ output.push(`Stderr: ${stderr.substring(0, 1000)}`);
1020
+ }
1021
+ output.push('');
1022
+ output.push(`✓ ${action} completed`);
1023
+ return output.join('\n');
1024
+ }
1025
+ catch (error) {
1026
+ if (error.killed) {
1027
+ return output.join('\n') + '\n\n(Command timed out - may still be running)';
1028
+ }
1029
+ return `Docker compose ${action} failed: ${error.message}\n${error.stderr || ''}`;
1030
+ }
1031
+ },
1032
+ },
1033
+ // ========================================================================
1034
+ // Project Scaffolding Tools
1035
+ // ========================================================================
1036
+ {
1037
+ name: 'project_init',
1038
+ description: 'Initialize a new project with common configurations (git, eslint, prettier, typescript, etc.)',
1039
+ parameters: {
1040
+ type: 'object',
1041
+ properties: {
1042
+ type: {
1043
+ type: 'string',
1044
+ enum: ['node', 'typescript', 'react', 'nextjs', 'python', 'go'],
1045
+ description: 'Project type',
1046
+ },
1047
+ name: {
1048
+ type: 'string',
1049
+ description: 'Project name',
1050
+ },
1051
+ git: {
1052
+ type: 'boolean',
1053
+ description: 'Initialize git repository (default: true)',
1054
+ },
1055
+ eslint: {
1056
+ type: 'boolean',
1057
+ description: 'Add ESLint configuration (for JS/TS projects, default: true)',
1058
+ },
1059
+ prettier: {
1060
+ type: 'boolean',
1061
+ description: 'Add Prettier configuration (default: true)',
1062
+ },
1063
+ docker: {
1064
+ type: 'boolean',
1065
+ description: 'Add Dockerfile and docker-compose.yml (default: false)',
1066
+ },
1067
+ ci: {
1068
+ type: 'string',
1069
+ enum: ['github', 'gitlab', 'none'],
1070
+ description: 'Add CI configuration (default: none)',
1071
+ },
1072
+ },
1073
+ required: ['type'],
1074
+ additionalProperties: false,
1075
+ },
1076
+ handler: async (args) => {
1077
+ const projectType = args['type'];
1078
+ const projectName = typeof args['name'] === 'string' ? args['name'] : 'my-project';
1079
+ const initGit = args['git'] !== false;
1080
+ const addEslint = args['eslint'] !== false;
1081
+ const addPrettier = args['prettier'] !== false;
1082
+ const addDocker = args['docker'] === true;
1083
+ const ci = typeof args['ci'] === 'string' ? args['ci'] : 'none';
1084
+ const output = [`# Project Initialization: ${projectName}`, ''];
1085
+ output.push(`Type: ${projectType}`);
1086
+ output.push('');
1087
+ try {
1088
+ // Initialize based on type
1089
+ output.push('## Creating Project');
1090
+ switch (projectType) {
1091
+ case 'node':
1092
+ await execAsync('npm init -y', { cwd: workingDir, timeout: 30000 });
1093
+ output.push('✓ Initialized npm project');
1094
+ break;
1095
+ case 'typescript':
1096
+ await execAsync('npm init -y && npm install typescript @types/node --save-dev', {
1097
+ cwd: workingDir,
1098
+ timeout: 120000,
1099
+ });
1100
+ await execAsync('npx tsc --init', { cwd: workingDir, timeout: 30000 });
1101
+ output.push('✓ Initialized TypeScript project');
1102
+ break;
1103
+ case 'react':
1104
+ await execAsync(`npx create-react-app ${projectName} --template typescript`, {
1105
+ cwd: workingDir,
1106
+ timeout: 300000,
1107
+ });
1108
+ output.push('✓ Created React app');
1109
+ break;
1110
+ case 'nextjs':
1111
+ await execAsync(`npx create-next-app@latest ${projectName} --typescript --eslint --tailwind --app`, {
1112
+ cwd: workingDir,
1113
+ timeout: 300000,
1114
+ });
1115
+ output.push('✓ Created Next.js app');
1116
+ break;
1117
+ case 'python':
1118
+ await execAsync('python3 -m venv .venv', { cwd: workingDir, timeout: 60000 });
1119
+ output.push('✓ Created Python virtual environment');
1120
+ break;
1121
+ case 'go':
1122
+ await execAsync(`go mod init ${projectName}`, { cwd: workingDir, timeout: 30000 });
1123
+ output.push('✓ Initialized Go module');
1124
+ break;
1125
+ }
1126
+ // Git
1127
+ if (initGit) {
1128
+ output.push('');
1129
+ output.push('## Git');
1130
+ try {
1131
+ await execAsync('git init', { cwd: workingDir, timeout: 10000 });
1132
+ output.push('✓ Initialized git repository');
1133
+ }
1134
+ catch {
1135
+ output.push('Git already initialized or failed');
1136
+ }
1137
+ }
1138
+ // ESLint (for JS/TS projects)
1139
+ if (addEslint && ['node', 'typescript', 'react', 'nextjs'].includes(projectType)) {
1140
+ output.push('');
1141
+ output.push('## ESLint');
1142
+ try {
1143
+ await execAsync('npm install eslint --save-dev', { cwd: workingDir, timeout: 120000 });
1144
+ output.push('✓ Installed ESLint');
1145
+ }
1146
+ catch (e) {
1147
+ output.push(`ESLint setup: ${e.message}`);
1148
+ }
1149
+ }
1150
+ // Prettier
1151
+ if (addPrettier) {
1152
+ output.push('');
1153
+ output.push('## Prettier');
1154
+ try {
1155
+ await execAsync('npm install prettier --save-dev', { cwd: workingDir, timeout: 120000 });
1156
+ output.push('✓ Installed Prettier');
1157
+ }
1158
+ catch (e) {
1159
+ output.push(`Prettier setup: ${e.message}`);
1160
+ }
1161
+ }
1162
+ // Docker
1163
+ if (addDocker) {
1164
+ output.push('');
1165
+ output.push('## Docker');
1166
+ output.push('Add Dockerfile and docker-compose.yml manually based on your needs');
1167
+ }
1168
+ // CI
1169
+ if (ci !== 'none') {
1170
+ output.push('');
1171
+ output.push(`## CI (${ci})`);
1172
+ if (ci === 'github') {
1173
+ output.push('Add .github/workflows/ci.yml for GitHub Actions');
1174
+ }
1175
+ else if (ci === 'gitlab') {
1176
+ output.push('Add .gitlab-ci.yml for GitLab CI');
1177
+ }
1178
+ }
1179
+ output.push('');
1180
+ output.push('## Summary');
1181
+ output.push(`✓ Project ${projectName} initialized as ${projectType}`);
1182
+ return output.join('\n');
1183
+ }
1184
+ catch (error) {
1185
+ return `Project initialization failed: ${error.message}`;
1186
+ }
1187
+ },
1188
+ },
1189
+ // ========================================================================
1190
+ // Environment & Secret Management
1191
+ // ========================================================================
1192
+ {
1193
+ name: 'env_check',
1194
+ description: 'Check environment configuration and required variables',
1195
+ parameters: {
1196
+ type: 'object',
1197
+ properties: {
1198
+ envFile: {
1199
+ type: 'string',
1200
+ description: 'Path to .env file to check (default: .env)',
1201
+ },
1202
+ exampleFile: {
1203
+ type: 'string',
1204
+ description: 'Path to .env.example to compare against',
1205
+ },
1206
+ requiredVars: {
1207
+ type: 'array',
1208
+ items: { type: 'string' },
1209
+ description: 'List of required environment variables to check',
1210
+ },
1211
+ },
1212
+ additionalProperties: false,
1213
+ },
1214
+ handler: async (args) => {
1215
+ const envFile = typeof args['envFile'] === 'string' ? args['envFile'] : '.env';
1216
+ const exampleFile = typeof args['exampleFile'] === 'string' ? args['exampleFile'] : '.env.example';
1217
+ const requiredVars = Array.isArray(args['requiredVars']) ? args['requiredVars'] : [];
1218
+ const output = ['# Environment Check', ''];
1219
+ try {
1220
+ const envPath = join(workingDir, envFile);
1221
+ const examplePath = join(workingDir, exampleFile);
1222
+ // Parse env file
1223
+ const parseEnvFile = (content) => {
1224
+ const vars = new Map();
1225
+ for (const line of content.split('\n')) {
1226
+ const trimmed = line.trim();
1227
+ if (trimmed && !trimmed.startsWith('#')) {
1228
+ const [key, ...valueParts] = trimmed.split('=');
1229
+ if (key) {
1230
+ vars.set(key.trim(), valueParts.join('=').trim());
1231
+ }
1232
+ }
1233
+ }
1234
+ return vars;
1235
+ };
1236
+ // Check .env exists
1237
+ let envVars = new Map();
1238
+ if (existsSync(envPath)) {
1239
+ const content = readFileSync(envPath, 'utf-8');
1240
+ envVars = parseEnvFile(content);
1241
+ output.push(`✓ Found ${envFile} with ${envVars.size} variables`);
1242
+ }
1243
+ else {
1244
+ output.push(`✗ ${envFile} not found`);
1245
+ }
1246
+ // Compare with example
1247
+ if (existsSync(examplePath)) {
1248
+ output.push('');
1249
+ output.push(`## Comparing with ${exampleFile}`);
1250
+ const exampleContent = readFileSync(examplePath, 'utf-8');
1251
+ const exampleVars = parseEnvFile(exampleContent);
1252
+ const missing = [];
1253
+ const extra = [];
1254
+ for (const key of exampleVars.keys()) {
1255
+ if (!envVars.has(key)) {
1256
+ missing.push(key);
1257
+ }
1258
+ }
1259
+ for (const key of envVars.keys()) {
1260
+ if (!exampleVars.has(key)) {
1261
+ extra.push(key);
1262
+ }
1263
+ }
1264
+ if (missing.length > 0) {
1265
+ output.push(`✗ Missing variables: ${missing.join(', ')}`);
1266
+ }
1267
+ else {
1268
+ output.push('✓ All example variables are defined');
1269
+ }
1270
+ if (extra.length > 0) {
1271
+ output.push(`ℹ Extra variables: ${extra.join(', ')}`);
1272
+ }
1273
+ }
1274
+ // Check required vars
1275
+ if (requiredVars.length > 0) {
1276
+ output.push('');
1277
+ output.push('## Required Variables');
1278
+ const missingRequired = [];
1279
+ const emptyRequired = [];
1280
+ for (const varName of requiredVars) {
1281
+ const value = envVars.get(varName) ?? process.env[varName];
1282
+ if (value === undefined) {
1283
+ missingRequired.push(varName);
1284
+ }
1285
+ else if (value === '') {
1286
+ emptyRequired.push(varName);
1287
+ }
1288
+ else {
1289
+ output.push(`✓ ${varName}: set`);
1290
+ }
1291
+ }
1292
+ if (missingRequired.length > 0) {
1293
+ output.push(`✗ Missing: ${missingRequired.join(', ')}`);
1294
+ }
1295
+ if (emptyRequired.length > 0) {
1296
+ output.push(`⚠ Empty: ${emptyRequired.join(', ')}`);
1297
+ }
1298
+ }
1299
+ // Check for sensitive patterns in .env
1300
+ output.push('');
1301
+ output.push('## Security Check');
1302
+ const sensitivePatterns = ['password', 'secret', 'key', 'token', 'credential'];
1303
+ const exposedSecrets = [];
1304
+ for (const [key, value] of envVars.entries()) {
1305
+ const lowerKey = key.toLowerCase();
1306
+ if (sensitivePatterns.some((p) => lowerKey.includes(p)) && value && value !== '***') {
1307
+ exposedSecrets.push(key);
1308
+ }
1309
+ }
1310
+ if (exposedSecrets.length > 0) {
1311
+ output.push(`⚠ Sensitive variables found: ${exposedSecrets.length}`);
1312
+ output.push('Make sure .env is in .gitignore');
1313
+ }
1314
+ else {
1315
+ output.push('✓ No obvious sensitive data patterns found');
1316
+ }
1317
+ return output.join('\n');
1318
+ }
1319
+ catch (error) {
1320
+ return `Environment check failed: ${error.message}`;
1321
+ }
1322
+ },
1323
+ },
1324
+ // ========================================================================
1325
+ // Process & Service Management
1326
+ // ========================================================================
1327
+ {
1328
+ name: 'service_status',
1329
+ description: 'Check status of common development services (databases, caches, etc.)',
1330
+ parameters: {
1331
+ type: 'object',
1332
+ properties: {
1333
+ services: {
1334
+ type: 'array',
1335
+ items: {
1336
+ type: 'string',
1337
+ enum: ['postgres', 'mysql', 'redis', 'mongodb', 'elasticsearch', 'docker', 'nginx', 'node'],
1338
+ },
1339
+ description: 'Services to check (default: auto-detect)',
1340
+ },
1341
+ ports: {
1342
+ type: 'array',
1343
+ items: { type: 'number' },
1344
+ description: 'Additional ports to check',
1345
+ },
1346
+ },
1347
+ additionalProperties: false,
1348
+ },
1349
+ handler: async (args) => {
1350
+ const services = Array.isArray(args['services']) ? args['services'] : [];
1351
+ const ports = Array.isArray(args['ports']) ? args['ports'] : [];
1352
+ const output = ['# Service Status', ''];
1353
+ const serviceConfig = {
1354
+ postgres: { port: 5432, process: 'postgres' },
1355
+ mysql: { port: 3306, process: 'mysql' },
1356
+ redis: { port: 6379, process: 'redis' },
1357
+ mongodb: { port: 27017, process: 'mongod' },
1358
+ elasticsearch: { port: 9200, process: 'java' },
1359
+ docker: { port: 2375, process: 'docker' },
1360
+ nginx: { port: 80, process: 'nginx' },
1361
+ node: { port: 3000, process: 'node' },
1362
+ };
1363
+ const checkPort = async (port) => {
1364
+ try {
1365
+ const { stdout } = await execAsync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null | head -1`, {
1366
+ cwd: workingDir,
1367
+ timeout: 5000,
1368
+ shell: '/bin/bash',
1369
+ });
1370
+ return stdout.trim().length > 0;
1371
+ }
1372
+ catch {
1373
+ return false;
1374
+ }
1375
+ };
1376
+ const checkProcess = async (name) => {
1377
+ try {
1378
+ const { stdout } = await execAsync(`pgrep -x "${name}" 2>/dev/null`, {
1379
+ cwd: workingDir,
1380
+ timeout: 5000,
1381
+ shell: '/bin/bash',
1382
+ });
1383
+ return stdout.trim().length > 0;
1384
+ }
1385
+ catch {
1386
+ return false;
1387
+ }
1388
+ };
1389
+ // Check specified services
1390
+ const servicesToCheck = services.length > 0 ? services : Object.keys(serviceConfig);
1391
+ output.push('## Services');
1392
+ for (const service of servicesToCheck) {
1393
+ const config = serviceConfig[service];
1394
+ if (!config)
1395
+ continue;
1396
+ const portOpen = await checkPort(config.port);
1397
+ const processRunning = config.process ? await checkProcess(config.process) : false;
1398
+ const status = portOpen ? '✓' : processRunning ? '⚠' : '✗';
1399
+ const detail = portOpen ? `listening on :${config.port}` : processRunning ? 'process running' : 'not detected';
1400
+ output.push(`${status} ${service}: ${detail}`);
1401
+ }
1402
+ // Check additional ports
1403
+ if (ports.length > 0) {
1404
+ output.push('');
1405
+ output.push('## Custom Ports');
1406
+ for (const port of ports) {
1407
+ const open = await checkPort(port);
1408
+ output.push(`${open ? '✓' : '✗'} Port ${port}: ${open ? 'in use' : 'available'}`);
1409
+ }
1410
+ }
1411
+ // Docker status
1412
+ output.push('');
1413
+ output.push('## Docker');
1414
+ try {
1415
+ const { stdout: dockerPs } = await execAsync('docker ps --format "{{.Names}}: {{.Status}}" 2>/dev/null', {
1416
+ cwd: workingDir,
1417
+ timeout: 10000,
1418
+ shell: '/bin/bash',
1419
+ });
1420
+ if (dockerPs.trim()) {
1421
+ output.push('Running containers:');
1422
+ dockerPs.split('\n').forEach((line) => {
1423
+ if (line.trim())
1424
+ output.push(` ${line.trim()}`);
1425
+ });
1426
+ }
1427
+ else {
1428
+ output.push('No running containers');
1429
+ }
1430
+ }
1431
+ catch {
1432
+ output.push('Docker not available');
1433
+ }
1434
+ return output.join('\n');
1435
+ },
1436
+ },
1437
+ {
1438
+ name: 'kill_port',
1439
+ description: 'Kill process running on a specific port',
1440
+ parameters: {
1441
+ type: 'object',
1442
+ properties: {
1443
+ port: {
1444
+ type: 'number',
1445
+ description: 'Port number to free up',
1446
+ },
1447
+ force: {
1448
+ type: 'boolean',
1449
+ description: 'Force kill with SIGKILL instead of SIGTERM (default: false)',
1450
+ },
1451
+ },
1452
+ required: ['port'],
1453
+ additionalProperties: false,
1454
+ },
1455
+ handler: async (args) => {
1456
+ const port = args['port'];
1457
+ const force = args['force'] === true;
1458
+ const output = [`# Kill Process on Port ${port}`, ''];
1459
+ try {
1460
+ // Find process on port
1461
+ const { stdout: lsofOut } = await execAsync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null`, {
1462
+ cwd: workingDir,
1463
+ timeout: 10000,
1464
+ shell: '/bin/bash',
1465
+ });
1466
+ if (!lsofOut.trim()) {
1467
+ return `No process found listening on port ${port}`;
1468
+ }
1469
+ output.push('## Process Info');
1470
+ output.push(lsofOut.trim());
1471
+ output.push('');
1472
+ // Extract PID
1473
+ const lines = lsofOut.split('\n').filter((l) => l.trim());
1474
+ if (lines.length < 2) {
1475
+ return `Could not find process on port ${port}`;
1476
+ }
1477
+ const pidMatch = lines[1]?.match(/\S+\s+(\d+)/);
1478
+ if (!pidMatch) {
1479
+ return `Could not extract PID from lsof output`;
1480
+ }
1481
+ const pid = pidMatch[1];
1482
+ const signal = force ? 'KILL' : 'TERM';
1483
+ output.push(`## Killing Process`);
1484
+ output.push(`PID: ${pid}`);
1485
+ output.push(`Signal: ${signal}`);
1486
+ await execAsync(`kill -${signal} ${pid}`, { cwd: workingDir, timeout: 10000 });
1487
+ output.push(`✓ Sent ${signal} signal to process ${pid}`);
1488
+ // Wait and verify
1489
+ await new Promise((r) => setTimeout(r, 1000));
1490
+ try {
1491
+ const { stdout: checkOut } = await execAsync(`lsof -i :${port} -sTCP:LISTEN 2>/dev/null`, {
1492
+ cwd: workingDir,
1493
+ timeout: 5000,
1494
+ shell: '/bin/bash',
1495
+ });
1496
+ if (checkOut.trim()) {
1497
+ output.push(`⚠ Process may still be running. Try with force=true`);
1498
+ }
1499
+ else {
1500
+ output.push(`✓ Port ${port} is now free`);
1501
+ }
1502
+ }
1503
+ catch {
1504
+ output.push(`✓ Port ${port} is now free`);
1505
+ }
1506
+ return output.join('\n');
1507
+ }
1508
+ catch (error) {
1509
+ return `Failed to kill process on port ${port}: ${error.message}`;
1510
+ }
1511
+ },
1512
+ },
1513
+ // ========================================================================
1514
+ // Dependency Management
1515
+ // ========================================================================
1516
+ {
1517
+ name: 'deps_audit',
1518
+ description: 'Audit dependencies for security vulnerabilities and outdated packages',
1519
+ parameters: {
1520
+ type: 'object',
1521
+ properties: {
1522
+ fix: {
1523
+ type: 'boolean',
1524
+ description: 'Attempt to fix vulnerabilities automatically (default: false)',
1525
+ },
1526
+ checkOutdated: {
1527
+ type: 'boolean',
1528
+ description: 'Check for outdated packages (default: true)',
1529
+ },
1530
+ level: {
1531
+ type: 'string',
1532
+ enum: ['info', 'low', 'moderate', 'high', 'critical'],
1533
+ description: 'Minimum vulnerability level to report (default: moderate)',
1534
+ },
1535
+ },
1536
+ additionalProperties: false,
1537
+ },
1538
+ handler: async (args) => {
1539
+ const fix = args['fix'] === true;
1540
+ const checkOutdated = args['checkOutdated'] !== false;
1541
+ const level = typeof args['level'] === 'string' ? args['level'] : 'moderate';
1542
+ const output = ['# Dependency Audit', ''];
1543
+ try {
1544
+ // npm audit
1545
+ output.push('## Security Audit');
1546
+ try {
1547
+ const auditCmd = fix ? 'npm audit fix' : `npm audit --audit-level=${level}`;
1548
+ const { stdout: auditOut, stderr: auditErr } = await execAsync(auditCmd, {
1549
+ cwd: workingDir,
1550
+ timeout: 120000,
1551
+ maxBuffer: 1024 * 1024 * 10,
1552
+ });
1553
+ output.push(auditOut.substring(0, 3000) || '✓ No vulnerabilities found');
1554
+ if (auditErr && !auditErr.includes('npm WARN')) {
1555
+ output.push(`Warnings: ${auditErr.substring(0, 500)}`);
1556
+ }
1557
+ }
1558
+ catch (e) {
1559
+ // npm audit returns non-zero if vulnerabilities found
1560
+ if (e.stdout) {
1561
+ output.push(e.stdout.substring(0, 3000));
1562
+ }
1563
+ else {
1564
+ output.push(`Audit check: ${e.message}`);
1565
+ }
1566
+ }
1567
+ // Check outdated
1568
+ if (checkOutdated) {
1569
+ output.push('');
1570
+ output.push('## Outdated Packages');
1571
+ try {
1572
+ const { stdout: outdatedOut } = await execAsync('npm outdated --json 2>/dev/null', {
1573
+ cwd: workingDir,
1574
+ timeout: 60000,
1575
+ shell: '/bin/bash',
1576
+ });
1577
+ if (outdatedOut.trim() && outdatedOut !== '{}') {
1578
+ const outdated = JSON.parse(outdatedOut);
1579
+ const entries = Object.entries(outdated);
1580
+ if (entries.length > 0) {
1581
+ output.push(`Found ${entries.length} outdated package(s):`);
1582
+ for (const [pkg, info] of entries.slice(0, 20)) {
1583
+ output.push(` ${pkg}: ${info.current} → ${info.wanted} (latest: ${info.latest})`);
1584
+ }
1585
+ if (entries.length > 20) {
1586
+ output.push(` ... and ${entries.length - 20} more`);
1587
+ }
1588
+ }
1589
+ else {
1590
+ output.push('✓ All packages are up to date');
1591
+ }
1592
+ }
1593
+ else {
1594
+ output.push('✓ All packages are up to date');
1595
+ }
1596
+ }
1597
+ catch {
1598
+ output.push('Could not check outdated packages');
1599
+ }
1600
+ }
1601
+ // License check
1602
+ output.push('');
1603
+ output.push('## License Info');
1604
+ try {
1605
+ const { stdout: licenseOut } = await execAsync('npx license-checker --summary 2>/dev/null', {
1606
+ cwd: workingDir,
1607
+ timeout: 60000,
1608
+ shell: '/bin/bash',
1609
+ });
1610
+ if (licenseOut.trim()) {
1611
+ output.push(licenseOut.substring(0, 1000));
1612
+ }
1613
+ }
1614
+ catch {
1615
+ output.push('License checker not available (install with: npm i -g license-checker)');
1616
+ }
1617
+ return output.join('\n');
1618
+ }
1619
+ catch (error) {
1620
+ return `Dependency audit failed: ${error.message}`;
1621
+ }
1622
+ },
1623
+ },
1624
+ {
1625
+ name: 'deps_update',
1626
+ description: 'Update dependencies to latest versions',
1627
+ parameters: {
1628
+ type: 'object',
1629
+ properties: {
1630
+ mode: {
1631
+ type: 'string',
1632
+ enum: ['patch', 'minor', 'major', 'latest'],
1633
+ description: 'Update mode - patch (safest), minor, major, or latest (default: minor)',
1634
+ },
1635
+ packages: {
1636
+ type: 'array',
1637
+ items: { type: 'string' },
1638
+ description: 'Specific packages to update (default: all)',
1639
+ },
1640
+ dryRun: {
1641
+ type: 'boolean',
1642
+ description: 'Show what would be updated without making changes',
1643
+ },
1644
+ runTests: {
1645
+ type: 'boolean',
1646
+ description: 'Run tests after update to verify (default: true)',
1647
+ },
1648
+ },
1649
+ additionalProperties: false,
1650
+ },
1651
+ handler: async (args) => {
1652
+ const mode = typeof args['mode'] === 'string' ? args['mode'] : 'minor';
1653
+ const packages = Array.isArray(args['packages']) ? args['packages'] : [];
1654
+ const dryRun = args['dryRun'] === true;
1655
+ const runTests = args['runTests'] !== false;
1656
+ const output = ['# Dependency Update', ''];
1657
+ output.push(`Mode: ${mode}`);
1658
+ if (dryRun)
1659
+ output.push('**DRY RUN**');
1660
+ output.push('');
1661
+ try {
1662
+ if (dryRun) {
1663
+ // Just check what would be updated
1664
+ output.push('## Would Update');
1665
+ const { stdout } = await execAsync('npm outdated --json 2>/dev/null || true', {
1666
+ cwd: workingDir,
1667
+ timeout: 60000,
1668
+ shell: '/bin/bash',
1669
+ });
1670
+ if (stdout.trim() && stdout !== '{}') {
1671
+ const outdated = JSON.parse(stdout);
1672
+ for (const [pkg, info] of Object.entries(outdated)) {
1673
+ if (packages.length === 0 || packages.includes(pkg)) {
1674
+ const target = mode === 'latest' ? info.latest : mode === 'major' ? info.latest : info.wanted;
1675
+ output.push(` ${pkg}: ${info.current} → ${target}`);
1676
+ }
1677
+ }
1678
+ }
1679
+ else {
1680
+ output.push('All packages are up to date');
1681
+ }
1682
+ return output.join('\n');
1683
+ }
1684
+ // Perform update
1685
+ output.push('## Updating');
1686
+ let updateCmd = 'npm update';
1687
+ if (packages.length > 0) {
1688
+ updateCmd = `npm update ${packages.join(' ')}`;
1689
+ }
1690
+ if (mode === 'latest' || mode === 'major') {
1691
+ // For major updates, use npm-check-updates
1692
+ output.push('Major/latest updates require npm-check-updates');
1693
+ try {
1694
+ const ncuCmd = packages.length > 0
1695
+ ? `npx npm-check-updates -u ${packages.join(' ')}`
1696
+ : 'npx npm-check-updates -u';
1697
+ await execAsync(ncuCmd, { cwd: workingDir, timeout: 120000 });
1698
+ await execAsync('npm install', { cwd: workingDir, timeout: 300000 });
1699
+ output.push('✓ Updated package.json and installed');
1700
+ }
1701
+ catch (e) {
1702
+ output.push(`Update failed: ${e.message}`);
1703
+ return output.join('\n');
1704
+ }
1705
+ }
1706
+ else {
1707
+ const { stdout: updateOut } = await execAsync(updateCmd, {
1708
+ cwd: workingDir,
1709
+ timeout: 300000,
1710
+ });
1711
+ output.push(updateOut.substring(0, 2000) || '✓ Updated');
1712
+ }
1713
+ // Run tests
1714
+ if (runTests) {
1715
+ output.push('');
1716
+ output.push('## Verifying');
1717
+ try {
1718
+ await execAsync('npm test', { cwd: workingDir, timeout: 300000 });
1719
+ output.push('✓ Tests passed');
1720
+ }
1721
+ catch (e) {
1722
+ output.push(`⚠ Tests failed: ${e.message}`);
1723
+ output.push('Consider reverting: git checkout package.json package-lock.json && npm install');
1724
+ }
1725
+ }
1726
+ return output.join('\n');
1727
+ }
1728
+ catch (error) {
1729
+ return `Dependency update failed: ${error.message}`;
1730
+ }
1731
+ },
1732
+ },
474
1733
  ];
475
1734
  }
476
1735
  //# sourceMappingURL=devTools.js.map