@edtools/cli 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/adapters/html/index.d.ts +2 -2
  2. package/dist/adapters/html/index.d.ts.map +1 -1
  3. package/dist/adapters/html/index.js +19 -2
  4. package/dist/adapters/html/index.js.map +1 -1
  5. package/dist/adapters/html/templates/base-layout.ejs +173 -0
  6. package/dist/adapters/html/templates/blog-index.html.ejs +170 -101
  7. package/dist/adapters/html/templates/blog-post-enhanced.ejs +655 -0
  8. package/dist/adapters/html/templates/blog-post-new.ejs +612 -0
  9. package/dist/adapters/html/templates/components/footer.ejs +330 -0
  10. package/dist/adapters/html/templates/components/header.ejs +208 -0
  11. package/dist/adapters/html/templates/components/social-share.ejs +202 -0
  12. package/dist/chunk-5N3D47CJ.js +823 -0
  13. package/dist/chunk-TROAGFSZ.js +824 -0
  14. package/dist/chunk-U77FH5BI.js +823 -0
  15. package/dist/cli/commands/config.d.ts +17 -0
  16. package/dist/cli/commands/config.d.ts.map +1 -0
  17. package/dist/cli/commands/config.js +140 -0
  18. package/dist/cli/commands/config.js.map +1 -0
  19. package/dist/cli/commands/generate.d.ts.map +1 -1
  20. package/dist/cli/commands/generate.js +57 -14
  21. package/dist/cli/commands/generate.js.map +1 -1
  22. package/dist/cli/commands/init.d.ts.map +1 -1
  23. package/dist/cli/commands/init.js +114 -0
  24. package/dist/cli/commands/init.js.map +1 -1
  25. package/dist/cli/index.js +552 -262
  26. package/dist/cli/index.js.map +1 -1
  27. package/dist/core/generator.d.ts +4 -1
  28. package/dist/core/generator.d.ts.map +1 -1
  29. package/dist/core/generator.js +114 -3
  30. package/dist/core/generator.js.map +1 -1
  31. package/dist/index.d.ts +87 -3
  32. package/dist/index.js +1 -1
  33. package/dist/types/adapter.d.ts +17 -2
  34. package/dist/types/adapter.d.ts.map +1 -1
  35. package/dist/types/adapter.js.map +1 -1
  36. package/dist/types/content.d.ts +66 -0
  37. package/dist/types/content.d.ts.map +1 -1
  38. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ContentGenerator
4
- } from "../chunk-BI3UJPWA.js";
4
+ } from "../chunk-TROAGFSZ.js";
5
5
  import "../chunk-INVECVSW.js";
6
6
 
7
7
  // src/cli/index.ts
8
8
  import { Command } from "commander";
9
- import chalk7 from "chalk";
10
9
 
11
10
  // src/cli/commands/init.ts
12
11
  import fs2 from "fs-extra";
@@ -372,6 +371,111 @@ async function initCommand(options) {
372
371
  default: "anthropic"
373
372
  }
374
373
  ]);
374
+ console.log(chalk2.cyan("\n\u{1F3A8} Blog Branding (optional - press Enter to skip):\n"));
375
+ const blogBrandingAnswers = await inquirer.prompt([
376
+ {
377
+ type: "input",
378
+ name: "blogTitle",
379
+ message: "Blog title:",
380
+ default: `${answers.name} Blog`
381
+ },
382
+ {
383
+ type: "input",
384
+ name: "blogDescription",
385
+ message: "Blog description:",
386
+ default: `Learn about ${answers.name} and best practices`
387
+ },
388
+ {
389
+ type: "input",
390
+ name: "language",
391
+ message: "Blog language (en, es, fr, etc.):",
392
+ default: "en"
393
+ },
394
+ {
395
+ type: "input",
396
+ name: "logo",
397
+ message: "Logo URL (optional):",
398
+ default: ""
399
+ },
400
+ {
401
+ type: "input",
402
+ name: "primaryColor",
403
+ message: "Primary color (hex):",
404
+ default: "#2563eb",
405
+ validate: (input) => {
406
+ if (!input) return true;
407
+ return /^#[0-9A-F]{6}$/i.test(input) || "Please enter a valid hex color (e.g., #2563eb)";
408
+ }
409
+ }
410
+ ]);
411
+ const { addNavigation } = await inquirer.prompt([
412
+ {
413
+ type: "confirm",
414
+ name: "addNavigation",
415
+ message: "Add navigation links?",
416
+ default: false
417
+ }
418
+ ]);
419
+ let navigation = [];
420
+ if (addNavigation) {
421
+ let addMore = true;
422
+ while (addMore) {
423
+ const navItem = await inquirer.prompt([
424
+ {
425
+ type: "input",
426
+ name: "label",
427
+ message: "Navigation link label:",
428
+ validate: (input) => input.length > 0 || "Label is required"
429
+ },
430
+ {
431
+ type: "input",
432
+ name: "url",
433
+ message: "Navigation link URL:",
434
+ validate: (input) => input.length > 0 || "URL is required"
435
+ }
436
+ ]);
437
+ navigation.push(navItem);
438
+ const { continueAdding } = await inquirer.prompt([
439
+ {
440
+ type: "confirm",
441
+ name: "continueAdding",
442
+ message: "Add another navigation link?",
443
+ default: false
444
+ }
445
+ ]);
446
+ addMore = continueAdding;
447
+ }
448
+ }
449
+ console.log(chalk2.cyan("\n\u{1F4F1} Social Media (optional):\n"));
450
+ const socialAnswers = await inquirer.prompt([
451
+ {
452
+ type: "input",
453
+ name: "twitter",
454
+ message: "Twitter/X URL:",
455
+ default: ""
456
+ },
457
+ {
458
+ type: "input",
459
+ name: "linkedin",
460
+ message: "LinkedIn URL:",
461
+ default: ""
462
+ },
463
+ {
464
+ type: "input",
465
+ name: "github",
466
+ message: "GitHub URL:",
467
+ default: ""
468
+ },
469
+ {
470
+ type: "input",
471
+ name: "youtube",
472
+ message: "YouTube URL:",
473
+ default: ""
474
+ }
475
+ ]);
476
+ const social = Object.fromEntries(
477
+ Object.entries(socialAnswers).filter(([_, value]) => value !== "")
478
+ );
375
479
  console.log(chalk2.cyan("\n\u{1F511} API Key Setup:\n"));
376
480
  const apiKeyAnswer = await inquirer.prompt([
377
481
  {
@@ -417,6 +521,16 @@ async function initCommand(options) {
417
521
  preferredProvider: ${JSON.stringify(finalProductInfo.preferredProvider)},
418
522
  },
419
523
 
524
+ blog: {
525
+ title: ${JSON.stringify(blogBrandingAnswers.blogTitle)},
526
+ description: ${JSON.stringify(blogBrandingAnswers.blogDescription)},
527
+ language: ${JSON.stringify(blogBrandingAnswers.language)},
528
+ logo: ${JSON.stringify(blogBrandingAnswers.logo)},
529
+ primaryColor: ${JSON.stringify(blogBrandingAnswers.primaryColor)},
530
+ navigation: ${JSON.stringify(navigation, null, 2)},
531
+ social: ${JSON.stringify(social, null, 2)},
532
+ },
533
+
420
534
  content: {
421
535
  outputDir: ${JSON.stringify(suggestedOutputDir)},
422
536
  generateBlog: true,
@@ -459,88 +573,267 @@ ${chalk2.gray("API key stored securely (gitignored)")}`;
459
573
  }
460
574
 
461
575
  // src/cli/commands/generate.ts
462
- import fs3 from "fs-extra";
463
- import path3 from "path";
576
+ import fs4 from "fs-extra";
577
+ import path4 from "path";
464
578
  import { pathToFileURL } from "url";
465
- import chalk3 from "chalk";
579
+ import chalk4 from "chalk";
466
580
  import ora2 from "ora";
467
581
  import Table from "cli-table3";
582
+ import inquirer3 from "inquirer";
583
+
584
+ // src/cli/commands/config.ts
585
+ import fs3 from "fs-extra";
586
+ import path3 from "path";
587
+ import chalk3 from "chalk";
468
588
  import inquirer2 from "inquirer";
589
+ async function promptForApiKey(projectPath, providedKey, providedProvider) {
590
+ const edtoolsDir = path3.join(projectPath, ".edtools");
591
+ const configPath = path3.join(edtoolsDir, "config.json");
592
+ await fs3.ensureDir(edtoolsDir);
593
+ let existingConfig = {};
594
+ if (await fs3.pathExists(configPath)) {
595
+ try {
596
+ existingConfig = await fs3.readJson(configPath);
597
+ } catch (error) {
598
+ console.log(chalk3.yellow("\u26A0\uFE0F Existing config is corrupted, will create new one"));
599
+ }
600
+ }
601
+ let apiKey = providedKey;
602
+ let provider = providedProvider || existingConfig.provider || "anthropic";
603
+ if (!apiKey) {
604
+ console.log(chalk3.cyan("\n\u{1F511} API Key Setup:\n"));
605
+ const providerAnswer = await inquirer2.prompt([
606
+ {
607
+ type: "list",
608
+ name: "provider",
609
+ message: "AI provider:",
610
+ choices: [
611
+ { name: "Claude (Anthropic) - Recommended", value: "anthropic" },
612
+ { name: "ChatGPT (OpenAI)", value: "openai" }
613
+ ],
614
+ default: provider
615
+ }
616
+ ]);
617
+ provider = providerAnswer.provider;
618
+ const keyAnswer = await inquirer2.prompt([
619
+ {
620
+ type: "password",
621
+ name: "apiKey",
622
+ message: provider === "anthropic" ? "Anthropic API Key (from https://console.anthropic.com):" : "OpenAI API Key (from https://platform.openai.com/api-keys):",
623
+ validate: (input) => {
624
+ if (!input || input.trim().length === 0) {
625
+ return "API key is required";
626
+ }
627
+ if (provider === "anthropic" && !input.startsWith("sk-ant-")) {
628
+ return 'Anthropic API keys should start with "sk-ant-"';
629
+ }
630
+ if (provider === "openai" && !input.startsWith("sk-")) {
631
+ return 'OpenAI API keys should start with "sk-"';
632
+ }
633
+ return true;
634
+ }
635
+ }
636
+ ]);
637
+ apiKey = keyAnswer.apiKey;
638
+ }
639
+ if (provider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
640
+ console.log(errorBox('Invalid API Key: Anthropic API keys should start with "sk-ant-"'));
641
+ return null;
642
+ }
643
+ if (provider === "openai" && !apiKey.startsWith("sk-")) {
644
+ console.log(errorBox('Invalid API Key: OpenAI API keys should start with "sk-"'));
645
+ return null;
646
+ }
647
+ const config = {
648
+ ...existingConfig,
649
+ apiKey,
650
+ provider
651
+ };
652
+ await fs3.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
653
+ const gitignorePath = path3.join(edtoolsDir, ".gitignore");
654
+ if (!await fs3.pathExists(gitignorePath)) {
655
+ await fs3.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
656
+ }
657
+ console.log("");
658
+ console.log(successBox(`API Key configured successfully for ${provider}!`));
659
+ console.log(chalk3.gray(`Stored in: ${chalk3.white(".edtools/config.json")} (gitignored)
660
+ `));
661
+ return { apiKey, provider };
662
+ }
663
+ async function configSetApiKeyCommand(options) {
664
+ const projectPath = path3.resolve(options.path);
665
+ const result = await promptForApiKey(projectPath, options.key, options.provider);
666
+ if (!result) {
667
+ console.log(chalk3.red("\n\u2717 API key setup cancelled or failed\n"));
668
+ process.exit(1);
669
+ }
670
+ }
671
+ async function configShowCommand(options) {
672
+ const projectPath = path3.resolve(options.path);
673
+ const edtoolsConfigPath = path3.join(projectPath, ".edtools", "config.json");
674
+ const mainConfigPath = path3.join(projectPath, "edtools.config.js");
675
+ console.log(chalk3.cyan.bold("\n\u{1F4CB} Edtools Configuration:\n"));
676
+ if (await fs3.pathExists(mainConfigPath)) {
677
+ console.log(chalk3.green("\u2713") + " edtools.config.js found");
678
+ console.log(chalk3.gray(` Location: ${mainConfigPath}
679
+ `));
680
+ } else {
681
+ console.log(chalk3.red("\u2717") + " edtools.config.js not found");
682
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools init")} to create it
683
+ `));
684
+ }
685
+ if (await fs3.pathExists(edtoolsConfigPath)) {
686
+ try {
687
+ const config = await fs3.readJson(edtoolsConfigPath);
688
+ const hasApiKey = config.apiKey && config.apiKey.length > 0;
689
+ const provider = config.provider || "anthropic";
690
+ console.log(chalk3.green("\u2713") + " API key configured");
691
+ console.log(chalk3.gray(` Provider: ${chalk3.white(provider)}`));
692
+ console.log(chalk3.gray(` Key: ${chalk3.white(hasApiKey ? config.apiKey.substring(0, 10) + "..." : "Not set")}`));
693
+ console.log(chalk3.gray(` Location: ${edtoolsConfigPath}
694
+ `));
695
+ } catch (error) {
696
+ console.log(chalk3.red("\u2717") + " API key config is corrupted");
697
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools config set-api-key")} to fix it
698
+ `));
699
+ }
700
+ } else {
701
+ console.log(chalk3.red("\u2717") + " API key not configured");
702
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools config set-api-key")} or ${chalk3.white("edtools init")}
703
+ `));
704
+ }
705
+ const anthropicEnv = process.env.ANTHROPIC_API_KEY;
706
+ const openaiEnv = process.env.OPENAI_API_KEY;
707
+ if (anthropicEnv || openaiEnv) {
708
+ console.log(chalk3.cyan("Environment variables:"));
709
+ if (anthropicEnv) {
710
+ console.log(chalk3.gray(` ANTHROPIC_API_KEY: ${chalk3.white(anthropicEnv.substring(0, 10) + "...")}`));
711
+ }
712
+ if (openaiEnv) {
713
+ console.log(chalk3.gray(` OPENAI_API_KEY: ${chalk3.white(openaiEnv.substring(0, 10) + "...")}`));
714
+ }
715
+ console.log("");
716
+ }
717
+ console.log(chalk3.cyan("\u{1F4A1} Tip:"));
718
+ console.log(chalk3.gray(" API keys are searched in this order:"));
719
+ console.log(chalk3.gray(" 1. --api-key flag"));
720
+ console.log(chalk3.gray(" 2. .edtools/config.json"));
721
+ console.log(chalk3.gray(" 3. Environment variables\n"));
722
+ }
723
+
724
+ // src/cli/commands/generate.ts
469
725
  async function generateCommand(options) {
470
- console.log(chalk3.cyan.bold("\n\u{1F4DD} Generating content...\n"));
726
+ console.log(chalk4.cyan.bold("\n\u{1F4DD} Generating content...\n"));
471
727
  const projectPath = process.cwd();
472
- const configPath = path3.join(projectPath, "edtools.config.js");
473
- if (!await fs3.pathExists(configPath)) {
474
- console.log(chalk3.red("\u2717 No edtools.config.js found"));
475
- console.log(chalk3.yellow(' Run "edtools init" first\n'));
728
+ const configPath = path4.join(projectPath, "edtools.config.js");
729
+ if (!await fs4.pathExists(configPath)) {
730
+ console.log(chalk4.red("\u2717 No edtools.config.js found"));
731
+ console.log(chalk4.yellow(' Run "edtools init" first\n'));
476
732
  process.exit(1);
477
733
  }
478
734
  const configUrl = pathToFileURL(configPath).href;
479
735
  const config = await import(configUrl);
480
736
  const productInfo = config.default.product;
481
737
  if (!productInfo || !productInfo.name) {
482
- console.log(chalk3.red("\u2717 Invalid configuration in edtools.config.js"));
738
+ console.log(chalk4.red("\u2717 Invalid configuration in edtools.config.js"));
483
739
  process.exit(1);
484
740
  }
485
741
  const provider = productInfo.preferredProvider || "anthropic";
486
742
  let storedApiKey;
487
- const edtoolsConfigPath = path3.join(projectPath, ".edtools", "config.json");
488
- if (await fs3.pathExists(edtoolsConfigPath)) {
743
+ const edtoolsConfigPath = path4.join(projectPath, ".edtools", "config.json");
744
+ if (await fs4.pathExists(edtoolsConfigPath)) {
489
745
  try {
490
- const edtoolsConfig = await fs3.readJson(edtoolsConfigPath);
746
+ const edtoolsConfig = await fs4.readJson(edtoolsConfigPath);
491
747
  storedApiKey = edtoolsConfig.apiKey;
492
748
  } catch (error) {
493
749
  }
494
750
  }
495
751
  let apiKey;
752
+ let actualProvider = provider;
496
753
  if (provider === "anthropic") {
497
754
  apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || storedApiKey;
498
755
  if (!apiKey) {
499
- console.log(chalk3.red("\u2717 No Anthropic API key found"));
500
- console.log(chalk3.yellow(' Run "edtools init" to configure your API key'));
501
- console.log(chalk3.yellow(" Or set ANTHROPIC_API_KEY environment variable"));
502
- console.log(chalk3.yellow(" Or use --api-key option\n"));
503
- process.exit(1);
756
+ console.log(chalk4.yellow("\n\u26A0\uFE0F No Anthropic API key configured\n"));
757
+ const { shouldConfigure } = await inquirer3.prompt([{
758
+ type: "confirm",
759
+ name: "shouldConfigure",
760
+ message: "Would you like to configure it now?",
761
+ default: true
762
+ }]);
763
+ if (shouldConfigure) {
764
+ const result = await promptForApiKey(projectPath);
765
+ if (result) {
766
+ apiKey = result.apiKey;
767
+ actualProvider = result.provider;
768
+ } else {
769
+ console.log(chalk4.red("\n\u2717 API key configuration failed\n"));
770
+ process.exit(1);
771
+ }
772
+ } else {
773
+ console.log(chalk4.cyan("\n\u{1F4A1} Quick fix options:\n"));
774
+ console.log(chalk4.gray(" 1. Run: ") + chalk4.white("edtools config set-api-key"));
775
+ console.log(chalk4.gray(" 2. Set env: ") + chalk4.white("export ANTHROPIC_API_KEY=sk-ant-..."));
776
+ console.log(chalk4.gray(" 3. Use flag: ") + chalk4.white("--api-key sk-ant-...\n"));
777
+ process.exit(1);
778
+ }
504
779
  }
505
780
  } else if (provider === "openai") {
506
781
  apiKey = options.apiKey || process.env.OPENAI_API_KEY || storedApiKey;
507
782
  if (!apiKey) {
508
- console.log(chalk3.red("\u2717 No OpenAI API key found"));
509
- console.log(chalk3.yellow(' Run "edtools init" to configure your API key'));
510
- console.log(chalk3.yellow(" Or set OPENAI_API_KEY environment variable"));
511
- console.log(chalk3.yellow(" Or use --api-key option\n"));
512
- process.exit(1);
783
+ console.log(chalk4.yellow("\n\u26A0\uFE0F No OpenAI API key configured\n"));
784
+ const { shouldConfigure } = await inquirer3.prompt([{
785
+ type: "confirm",
786
+ name: "shouldConfigure",
787
+ message: "Would you like to configure it now?",
788
+ default: true
789
+ }]);
790
+ if (shouldConfigure) {
791
+ const result = await promptForApiKey(projectPath);
792
+ if (result) {
793
+ apiKey = result.apiKey;
794
+ actualProvider = result.provider;
795
+ } else {
796
+ console.log(chalk4.red("\n\u2717 API key configuration failed\n"));
797
+ process.exit(1);
798
+ }
799
+ } else {
800
+ console.log(chalk4.cyan("\n\u{1F4A1} Quick fix options:\n"));
801
+ console.log(chalk4.gray(" 1. Run: ") + chalk4.white("edtools config set-api-key"));
802
+ console.log(chalk4.gray(" 2. Set env: ") + chalk4.white("export OPENAI_API_KEY=sk-..."));
803
+ console.log(chalk4.gray(" 3. Use flag: ") + chalk4.white("--api-key sk-...\n"));
804
+ process.exit(1);
805
+ }
513
806
  }
514
807
  }
515
808
  let topics = options.topics;
516
809
  if (options.fromCsv) {
517
- const opportunitiesPath = path3.join(projectPath, ".edtools", "opportunities.json");
518
- if (!await fs3.pathExists(opportunitiesPath)) {
519
- console.log(chalk3.red("\u2717 No CSV analysis found"));
520
- console.log(chalk3.yellow(' Run "edtools analyze" first to analyze your GSC data\n'));
810
+ const opportunitiesPath = path4.join(projectPath, ".edtools", "opportunities.json");
811
+ if (!await fs4.pathExists(opportunitiesPath)) {
812
+ console.log(chalk4.red("\u2717 No CSV analysis found"));
813
+ console.log(chalk4.yellow(' Run "edtools analyze" first to analyze your GSC data\n'));
521
814
  process.exit(1);
522
815
  }
523
816
  try {
524
- const oppData = await fs3.readJson(opportunitiesPath);
817
+ const oppData = await fs4.readJson(opportunitiesPath);
525
818
  topics = oppData.opportunities.slice(0, parseInt(options.posts, 10)).map((opp) => opp.suggestedTitle);
526
- console.log(chalk3.cyan("\u{1F4CA} Using topics from CSV analysis:\n"));
819
+ console.log(chalk4.cyan("\u{1F4CA} Using topics from CSV analysis:\n"));
527
820
  topics.forEach((topic, i) => {
528
- console.log(` ${chalk3.cyan((i + 1).toString() + ".")} ${chalk3.white(topic)}`);
821
+ console.log(` ${chalk4.cyan((i + 1).toString() + ".")} ${chalk4.white(topic)}`);
529
822
  });
530
823
  console.log("");
531
824
  } catch (error) {
532
- console.log(chalk3.red("\u2717 Failed to load CSV analysis"));
825
+ console.log(chalk4.red("\u2717 Failed to load CSV analysis"));
533
826
  process.exit(1);
534
827
  }
535
828
  }
536
829
  const count = parseInt(options.posts, 10);
537
830
  if (isNaN(count) || count < 1 || count > 10) {
538
- console.log(chalk3.red("\u2717 Invalid number of posts (must be 1-10)"));
831
+ console.log(chalk4.red("\u2717 Invalid number of posts (must be 1-10)"));
539
832
  process.exit(1);
540
833
  }
541
834
  if (count > 5) {
542
- console.log(chalk3.yellow(`\u26A0\uFE0F Generating ${count} posts at once may trigger spam detection`));
543
- console.log(chalk3.yellow(" Recommended: 3-5 posts per week\n"));
835
+ console.log(chalk4.yellow(`\u26A0\uFE0F Generating ${count} posts at once may trigger spam detection`));
836
+ console.log(chalk4.yellow(" Recommended: 3-5 posts per week\n"));
544
837
  }
545
838
  let outputDirConfig;
546
839
  let outputDirSource;
@@ -554,48 +847,48 @@ async function generateCommand(options) {
554
847
  outputDirConfig = "./blog";
555
848
  outputDirSource = "default";
556
849
  }
557
- const outputDir = path3.resolve(projectPath, outputDirConfig);
558
- await fs3.ensureDir(outputDir);
850
+ const outputDir = path4.resolve(projectPath, outputDirConfig);
851
+ await fs4.ensureDir(outputDir);
559
852
  const hostingConfig = detectHostingConfig(projectPath);
560
853
  if (hostingConfig) {
561
854
  const validation = validateOutputDir(outputDirConfig, hostingConfig);
562
855
  if (!validation.valid) {
563
- console.log(chalk3.yellow.bold("\n\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk3.white(validation.platform));
564
- console.log(chalk3.yellow(" - Public directory: ") + chalk3.white(validation.publicDir));
565
- console.log(chalk3.yellow(" - Your outputDir: ") + chalk3.white(validation.currentOutputDir));
566
- console.log(chalk3.yellow(" - Problem: Files outside ") + chalk3.white(validation.publicDir + "/") + chalk3.yellow(" won't be accessible"));
856
+ console.log(chalk4.yellow.bold("\n\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk4.white(validation.platform));
857
+ console.log(chalk4.yellow(" - Public directory: ") + chalk4.white(validation.publicDir));
858
+ console.log(chalk4.yellow(" - Your outputDir: ") + chalk4.white(validation.currentOutputDir));
859
+ console.log(chalk4.yellow(" - Problem: Files outside ") + chalk4.white(validation.publicDir + "/") + chalk4.yellow(" won't be accessible"));
567
860
  console.log("");
568
- console.log(chalk3.red.bold("\u274C ISSUE: Output directory misconfiguration"));
569
- console.log(chalk3.red(" Generated files will return 404 in production"));
861
+ console.log(chalk4.red.bold("\u274C ISSUE: Output directory misconfiguration"));
862
+ console.log(chalk4.red(" Generated files will return 404 in production"));
570
863
  console.log("");
571
- console.log(chalk3.cyan.bold("\u{1F4DD} Suggested fix:"));
572
- console.log(chalk3.cyan(" Update edtools.config.js:"));
864
+ console.log(chalk4.cyan.bold("\u{1F4DD} Suggested fix:"));
865
+ console.log(chalk4.cyan(" Update edtools.config.js:"));
573
866
  console.log("");
574
- console.log(chalk3.gray(" content: {"));
575
- console.log(chalk3.green(` outputDir: '${validation.suggestion}'`) + chalk3.gray(" // \u2705 Correct path"));
576
- console.log(chalk3.gray(" }"));
867
+ console.log(chalk4.gray(" content: {"));
868
+ console.log(chalk4.green(` outputDir: '${validation.suggestion}'`) + chalk4.gray(" // \u2705 Correct path"));
869
+ console.log(chalk4.gray(" }"));
577
870
  console.log("");
578
- const answer = await inquirer2.prompt([{
871
+ const answer = await inquirer3.prompt([{
579
872
  type: "confirm",
580
873
  name: "continue",
581
874
  message: "Continue anyway?",
582
875
  default: false
583
876
  }]);
584
877
  if (!answer.continue) {
585
- console.log(chalk3.yellow("\n\u2717 Generation cancelled\n"));
878
+ console.log(chalk4.yellow("\n\u2717 Generation cancelled\n"));
586
879
  process.exit(0);
587
880
  }
588
881
  console.log("");
589
882
  }
590
883
  }
591
- console.log(chalk3.cyan("Configuration:"));
592
- console.log(` Product: ${chalk3.white(productInfo.name)}`);
593
- console.log(` Category: ${chalk3.white(productInfo.category)}`);
594
- console.log(` AI Provider: ${chalk3.white(provider === "anthropic" ? "Claude (Anthropic)" : "ChatGPT (OpenAI)")}`);
595
- console.log(` Posts to generate: ${chalk3.white(count)}`);
596
- console.log(` Output directory: ${chalk3.white(outputDir)} ${chalk3.gray("(" + outputDirSource + ")")}
884
+ console.log(chalk4.cyan("Configuration:"));
885
+ console.log(` Product: ${chalk4.white(productInfo.name)}`);
886
+ console.log(` Category: ${chalk4.white(productInfo.category)}`);
887
+ console.log(` AI Provider: ${chalk4.white(actualProvider === "anthropic" ? "Claude (Anthropic)" : "ChatGPT (OpenAI)")}`);
888
+ console.log(` Posts to generate: ${chalk4.white(count)}`);
889
+ console.log(` Output directory: ${chalk4.white(outputDir)} ${chalk4.gray("(" + outputDirSource + ")")}
597
890
  `);
598
- const generator = new ContentGenerator(apiKey, provider);
891
+ const generator = new ContentGenerator(apiKey, actualProvider);
599
892
  const generateConfig = {
600
893
  productInfo,
601
894
  topics,
@@ -604,20 +897,22 @@ async function generateCommand(options) {
604
897
  projectPath,
605
898
  avoidDuplicates: true,
606
899
  similarityThreshold: 0.85,
607
- provider,
900
+ provider: actualProvider,
608
901
  apiKey,
609
- dryRun: options.dryRun
902
+ dryRun: options.dryRun,
903
+ blogConfig: config.default.blog
904
+ // Pass blog config from edtools.config.js
610
905
  };
611
- const providerName = provider === "anthropic" ? "Claude" : "ChatGPT";
906
+ const providerName = actualProvider === "anthropic" ? "Claude" : "ChatGPT";
612
907
  const spinner = ora2(`Generating content with ${providerName}...`).start();
613
908
  try {
614
909
  const result = await generator.generate(generateConfig);
615
910
  if (result.success && result.posts.length > 0) {
616
911
  const dryRunMode = result.dryRun || false;
617
- spinner.succeed(chalk3.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
912
+ spinner.succeed(chalk4.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
618
913
  console.log("");
619
914
  if (dryRunMode) {
620
- console.log(chalk3.yellow.bold("\u{1F50D} DRY RUN MODE - No files were written\n"));
915
+ console.log(chalk4.yellow.bold("\u{1F50D} DRY RUN MODE - No files were written\n"));
621
916
  }
622
917
  if (options.report === "json" || options.report === "pretty") {
623
918
  const report = {
@@ -656,11 +951,11 @@ async function generateCommand(options) {
656
951
  } else {
657
952
  const table = new Table({
658
953
  head: [
659
- chalk3.cyan.bold("#"),
660
- chalk3.cyan.bold("Title"),
661
- chalk3.cyan.bold("Words"),
662
- chalk3.cyan.bold("SEO"),
663
- dryRunMode ? chalk3.cyan.bold("Would Create") : chalk3.cyan.bold("Path")
954
+ chalk4.cyan.bold("#"),
955
+ chalk4.cyan.bold("Title"),
956
+ chalk4.cyan.bold("Words"),
957
+ chalk4.cyan.bold("SEO"),
958
+ dryRunMode ? chalk4.cyan.bold("Would Create") : chalk4.cyan.bold("Path")
664
959
  ],
665
960
  colWidths: [5, 35, 10, 10, 45],
666
961
  wordWrap: true,
@@ -672,29 +967,29 @@ async function generateCommand(options) {
672
967
  result.posts.forEach((post, i) => {
673
968
  const scoreColor = getScoreColorFn(post.seoScore);
674
969
  table.push([
675
- chalk3.gray((i + 1).toString()),
676
- chalk3.white(truncateTitle(post.title, 33)),
677
- chalk3.white(post.wordCount.toString()),
970
+ chalk4.gray((i + 1).toString()),
971
+ chalk4.white(truncateTitle(post.title, 33)),
972
+ chalk4.white(post.wordCount.toString()),
678
973
  scoreColor(`${post.seoScore}/100`),
679
- chalk3.gray(post.path)
974
+ chalk4.gray(post.path)
680
975
  ]);
681
976
  });
682
977
  console.log(table.toString());
683
978
  console.log("");
684
979
  if (result.stats) {
685
- console.log(chalk3.cyan.bold("Statistics:\n"));
686
- console.log(` Total words: ${chalk3.white(result.stats.totalWords)}`);
980
+ console.log(chalk4.cyan.bold("Statistics:\n"));
981
+ console.log(` Total words: ${chalk4.white(result.stats.totalWords)}`);
687
982
  console.log(` Avg SEO score: ${getScoreColorFn(result.stats.avgSeoScore)(result.stats.avgSeoScore + "/100")}`);
688
- console.log(` Avg read time: ${chalk3.white(result.stats.avgReadTime)}`);
983
+ console.log(` Avg read time: ${chalk4.white(result.stats.avgReadTime)}`);
689
984
  console.log("");
690
985
  }
691
986
  if (dryRunMode) {
692
- console.log(chalk3.yellow.bold("Would create:\n"));
987
+ console.log(chalk4.yellow.bold("Would create:\n"));
693
988
  if (result.manifestPath) {
694
- console.log(` ${chalk3.yellow("\u2022")} ${chalk3.white(result.manifestPath)} (manifest with ${result.posts.length} posts)`);
989
+ console.log(` ${chalk4.yellow("\u2022")} ${chalk4.white(result.manifestPath)} (manifest with ${result.posts.length} posts)`);
695
990
  }
696
991
  if (result.sitemapPath) {
697
- console.log(` ${chalk3.yellow("\u2022")} ${chalk3.white(result.sitemapPath)} (sitemap with ${result.posts.length} URLs)`);
992
+ console.log(` ${chalk4.yellow("\u2022")} ${chalk4.white(result.sitemapPath)} (sitemap with ${result.posts.length} URLs)`);
698
993
  }
699
994
  console.log("");
700
995
  }
@@ -706,21 +1001,21 @@ async function generateCommand(options) {
706
1001
  console.log(warningBox(warningText));
707
1002
  }
708
1003
  if (dryRunMode) {
709
- console.log(chalk3.cyan.bold("Next step:"));
710
- console.log(` ${chalk3.cyan("\u2022")} Run without ${chalk3.white("--dry-run")} to generate files
1004
+ console.log(chalk4.cyan.bold("Next step:"));
1005
+ console.log(` ${chalk4.cyan("\u2022")} Run without ${chalk4.white("--dry-run")} to generate files
711
1006
  `);
712
1007
  } else {
713
- console.log(chalk3.cyan.bold("Next steps:"));
714
- console.log(` ${chalk3.cyan("1.")} Review generated content in ${chalk3.white(outputDir)}`);
715
- console.log(` ${chalk3.cyan("2.")} Edit posts to add personal experience/expertise`);
716
- console.log(` ${chalk3.cyan("3.")} Deploy to your website`);
1008
+ console.log(chalk4.cyan.bold("Next steps:"));
1009
+ console.log(` ${chalk4.cyan("1.")} Review generated content in ${chalk4.white(outputDir)}`);
1010
+ console.log(` ${chalk4.cyan("2.")} Edit posts to add personal experience/expertise`);
1011
+ console.log(` ${chalk4.cyan("3.")} Deploy to your website`);
717
1012
  console.log("");
718
- console.log(chalk3.yellow(`\u{1F4A1} Tip: Wait 3-7 days before generating more posts to avoid spam detection
1013
+ console.log(chalk4.yellow(`\u{1F4A1} Tip: Wait 3-7 days before generating more posts to avoid spam detection
719
1014
  `));
720
1015
  }
721
1016
  }
722
1017
  } else {
723
- spinner.fail(chalk3.bold("Failed to generate content"));
1018
+ spinner.fail(chalk4.bold("Failed to generate content"));
724
1019
  if (result.errors && result.errors.length > 0) {
725
1020
  const errorText = result.errors.join("\n");
726
1021
  console.log(errorBox(errorText));
@@ -728,16 +1023,16 @@ async function generateCommand(options) {
728
1023
  process.exit(1);
729
1024
  }
730
1025
  } catch (error) {
731
- spinner.fail(chalk3.bold("Error during generation"));
1026
+ spinner.fail(chalk4.bold("Error during generation"));
732
1027
  console.log(errorBox(error.message));
733
1028
  process.exit(1);
734
1029
  }
735
1030
  }
736
1031
  function getScoreColorFn(score) {
737
- if (score >= 90) return chalk3.green;
738
- if (score >= 75) return chalk3.yellow;
739
- if (score >= 60) return chalk3.hex("#FFA500");
740
- return chalk3.red;
1032
+ if (score >= 90) return chalk4.green;
1033
+ if (score >= 75) return chalk4.yellow;
1034
+ if (score >= 60) return chalk4.hex("#FFA500");
1035
+ return chalk4.red;
741
1036
  }
742
1037
  function truncateTitle(title, maxLen) {
743
1038
  if (title.length <= maxLen) return title;
@@ -745,17 +1040,17 @@ function truncateTitle(title, maxLen) {
745
1040
  }
746
1041
 
747
1042
  // src/cli/commands/analyze.ts
748
- import fs5 from "fs-extra";
749
- import path4 from "path";
750
- import chalk4 from "chalk";
1043
+ import fs6 from "fs-extra";
1044
+ import path5 from "path";
1045
+ import chalk5 from "chalk";
751
1046
  import ora3 from "ora";
752
1047
  import Table2 from "cli-table3";
753
1048
 
754
1049
  // src/integrations/gsc-csv-analyzer.ts
755
- import fs4 from "fs-extra";
1050
+ import fs5 from "fs-extra";
756
1051
  import { parse } from "csv-parse/sync";
757
1052
  async function parseGSCCSV(filePath) {
758
- const content = await fs4.readFile(filePath, "utf-8");
1053
+ const content = await fs5.readFile(filePath, "utf-8");
759
1054
  const records = parse(content, {
760
1055
  columns: true,
761
1056
  skip_empty_lines: true,
@@ -851,34 +1146,34 @@ async function saveOpportunities(opportunities, outputPath) {
851
1146
  count: opportunities.length,
852
1147
  opportunities
853
1148
  };
854
- await fs4.writeJson(outputPath, data, { spaces: 2 });
1149
+ await fs5.writeJson(outputPath, data, { spaces: 2 });
855
1150
  }
856
1151
 
857
1152
  // src/cli/commands/analyze.ts
858
1153
  async function analyzeCommand(options) {
859
- console.log(chalk4.cyan.bold("\n\u{1F4CA} Analyzing Google Search Console Data...\n"));
1154
+ console.log(chalk5.cyan.bold("\n\u{1F4CA} Analyzing Google Search Console Data...\n"));
860
1155
  const projectPath = process.cwd();
861
1156
  let csvPath;
862
1157
  if (options.file) {
863
- csvPath = path4.resolve(options.file);
1158
+ csvPath = path5.resolve(options.file);
864
1159
  } else {
865
- const files = await fs5.readdir(projectPath);
1160
+ const files = await fs6.readdir(projectPath);
866
1161
  const csvFiles = files.filter((f) => f.endsWith(".csv") && f.toLowerCase().includes("search"));
867
1162
  if (csvFiles.length === 0) {
868
- console.log(chalk4.red("\u2717 No CSV file found"));
869
- console.log(chalk4.yellow(" Please provide a CSV file using --file option"));
870
- console.log(chalk4.yellow(" Example: edtools analyze --file search-console-data.csv\n"));
1163
+ console.log(chalk5.red("\u2717 No CSV file found"));
1164
+ console.log(chalk5.yellow(" Please provide a CSV file using --file option"));
1165
+ console.log(chalk5.yellow(" Example: edtools analyze --file search-console-data.csv\n"));
871
1166
  process.exit(1);
872
1167
  }
873
1168
  if (csvFiles.length > 1) {
874
- console.log(chalk4.yellow("\u26A0 Multiple CSV files found:"));
1169
+ console.log(chalk5.yellow("\u26A0 Multiple CSV files found:"));
875
1170
  csvFiles.forEach((f) => console.log(` - ${f}`));
876
- console.log(chalk4.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
1171
+ console.log(chalk5.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
877
1172
  }
878
- csvPath = path4.join(projectPath, csvFiles[0]);
1173
+ csvPath = path5.join(projectPath, csvFiles[0]);
879
1174
  }
880
- if (!await fs5.pathExists(csvPath)) {
881
- console.log(chalk4.red(`\u2717 File not found: ${csvPath}
1175
+ if (!await fs6.pathExists(csvPath)) {
1176
+ console.log(chalk5.red(`\u2717 File not found: ${csvPath}
882
1177
  `));
883
1178
  process.exit(1);
884
1179
  }
@@ -886,10 +1181,10 @@ async function analyzeCommand(options) {
886
1181
  let data;
887
1182
  try {
888
1183
  data = await parseGSCCSV(csvPath);
889
- spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(path4.basename(csvPath))}`);
1184
+ spinner.succeed(`Parsed ${chalk5.white(data.length)} keywords from ${chalk5.white(path5.basename(csvPath))}`);
890
1185
  } catch (error) {
891
1186
  spinner.fail("Failed to parse CSV");
892
- console.log(chalk4.red(`
1187
+ console.log(chalk5.red(`
893
1188
  Error: ${error.message}
894
1189
  `));
895
1190
  process.exit(1);
@@ -904,7 +1199,7 @@ Error: ${error.message}
904
1199
  analyzeSpinner.succeed("Analysis complete!");
905
1200
  console.log("");
906
1201
  const metricsTable = new Table2({
907
- head: [chalk4.cyan.bold("Metric"), chalk4.cyan.bold("Value")],
1202
+ head: [chalk5.cyan.bold("Metric"), chalk5.cyan.bold("Value")],
908
1203
  colWidths: [30, 20],
909
1204
  style: {
910
1205
  head: [],
@@ -912,28 +1207,28 @@ Error: ${error.message}
912
1207
  }
913
1208
  });
914
1209
  metricsTable.push(
915
- ["Total Keywords", chalk4.white(analysis.totalQueries.toLocaleString())],
916
- ["Total Impressions", chalk4.white(analysis.totalImpressions.toLocaleString())],
917
- ["Total Clicks", chalk4.white(analysis.totalClicks.toLocaleString())],
918
- ["Average CTR", chalk4.white((analysis.avgCTR * 100).toFixed(2) + "%")],
919
- ["Average Position", chalk4.white(analysis.avgPosition.toFixed(1))]
1210
+ ["Total Keywords", chalk5.white(analysis.totalQueries.toLocaleString())],
1211
+ ["Total Impressions", chalk5.white(analysis.totalImpressions.toLocaleString())],
1212
+ ["Total Clicks", chalk5.white(analysis.totalClicks.toLocaleString())],
1213
+ ["Average CTR", chalk5.white((analysis.avgCTR * 100).toFixed(2) + "%")],
1214
+ ["Average Position", chalk5.white(analysis.avgPosition.toFixed(1))]
920
1215
  );
921
- console.log(chalk4.bold.cyan("\u{1F4C8} Overall Metrics\n"));
1216
+ console.log(chalk5.bold.cyan("\u{1F4C8} Overall Metrics\n"));
922
1217
  console.log(metricsTable.toString());
923
1218
  console.log("");
924
1219
  if (analysis.opportunities.length === 0) {
925
1220
  console.log(warningBox("No opportunities found with current criteria.\nTry lowering --min-impressions or increasing --min-position."));
926
1221
  } else {
927
- console.log(chalk4.bold.cyan(`\u{1F3AF} Top ${analysis.opportunities.length} Content Opportunities
1222
+ console.log(chalk5.bold.cyan(`\u{1F3AF} Top ${analysis.opportunities.length} Content Opportunities
928
1223
  `));
929
1224
  const oppTable = new Table2({
930
1225
  head: [
931
- chalk4.cyan.bold("#"),
932
- chalk4.cyan.bold("Keyword"),
933
- chalk4.cyan.bold("Impressions"),
934
- chalk4.cyan.bold("Position"),
935
- chalk4.cyan.bold("Potential"),
936
- chalk4.cyan.bold("Priority")
1226
+ chalk5.cyan.bold("#"),
1227
+ chalk5.cyan.bold("Keyword"),
1228
+ chalk5.cyan.bold("Impressions"),
1229
+ chalk5.cyan.bold("Position"),
1230
+ chalk5.cyan.bold("Potential"),
1231
+ chalk5.cyan.bold("Priority")
937
1232
  ],
938
1233
  colWidths: [5, 40, 14, 12, 12, 12],
939
1234
  wordWrap: true,
@@ -943,34 +1238,34 @@ Error: ${error.message}
943
1238
  }
944
1239
  });
945
1240
  analysis.opportunities.forEach((opp, i) => {
946
- const priorityColor = opp.priority === "high" ? chalk4.green : opp.priority === "medium" ? chalk4.yellow : chalk4.gray;
1241
+ const priorityColor = opp.priority === "high" ? chalk5.green : opp.priority === "medium" ? chalk5.yellow : chalk5.gray;
947
1242
  oppTable.push([
948
- chalk4.gray((i + 1).toString()),
949
- chalk4.white(opp.keyword),
950
- chalk4.cyan(opp.impressions.toLocaleString()),
951
- chalk4.yellow(opp.position.toFixed(1)),
952
- chalk4.green(`+${opp.potentialClicks}`),
1243
+ chalk5.gray((i + 1).toString()),
1244
+ chalk5.white(opp.keyword),
1245
+ chalk5.cyan(opp.impressions.toLocaleString()),
1246
+ chalk5.yellow(opp.position.toFixed(1)),
1247
+ chalk5.green(`+${opp.potentialClicks}`),
953
1248
  priorityColor(opp.priority.toUpperCase())
954
1249
  ]);
955
1250
  });
956
1251
  console.log(oppTable.toString());
957
1252
  console.log("");
958
- console.log(chalk4.bold.cyan("\u{1F4A1} Suggested Blog Post Titles\n"));
1253
+ console.log(chalk5.bold.cyan("\u{1F4A1} Suggested Blog Post Titles\n"));
959
1254
  analysis.opportunities.slice(0, 5).forEach((opp, i) => {
960
- console.log(` ${chalk4.cyan((i + 1).toString() + ".")} ${chalk4.white(opp.suggestedTitle)}`);
961
- console.log(` ${chalk4.gray(opp.reason)}
1255
+ console.log(` ${chalk5.cyan((i + 1).toString() + ".")} ${chalk5.white(opp.suggestedTitle)}`);
1256
+ console.log(` ${chalk5.gray(opp.reason)}
962
1257
  `);
963
1258
  });
964
1259
  }
965
1260
  if (analysis.topKeywords.length > 0) {
966
- console.log(chalk4.bold.cyan("\u2B50 Top Performing Keywords (by clicks)\n"));
1261
+ console.log(chalk5.bold.cyan("\u2B50 Top Performing Keywords (by clicks)\n"));
967
1262
  const topTable = new Table2({
968
1263
  head: [
969
- chalk4.cyan.bold("#"),
970
- chalk4.cyan.bold("Keyword"),
971
- chalk4.cyan.bold("Clicks"),
972
- chalk4.cyan.bold("Impressions"),
973
- chalk4.cyan.bold("CTR")
1264
+ chalk5.cyan.bold("#"),
1265
+ chalk5.cyan.bold("Keyword"),
1266
+ chalk5.cyan.bold("Clicks"),
1267
+ chalk5.cyan.bold("Impressions"),
1268
+ chalk5.cyan.bold("CTR")
974
1269
  ],
975
1270
  colWidths: [5, 40, 12, 14, 12],
976
1271
  wordWrap: true,
@@ -981,39 +1276,39 @@ Error: ${error.message}
981
1276
  });
982
1277
  analysis.topKeywords.slice(0, 5).forEach((kw, i) => {
983
1278
  topTable.push([
984
- chalk4.gray((i + 1).toString()),
985
- chalk4.white(kw.query),
986
- chalk4.green(kw.clicks.toString()),
987
- chalk4.cyan(kw.impressions.toLocaleString()),
988
- chalk4.yellow((kw.ctr * 100).toFixed(2) + "%")
1279
+ chalk5.gray((i + 1).toString()),
1280
+ chalk5.white(kw.query),
1281
+ chalk5.green(kw.clicks.toString()),
1282
+ chalk5.cyan(kw.impressions.toLocaleString()),
1283
+ chalk5.yellow((kw.ctr * 100).toFixed(2) + "%")
989
1284
  ]);
990
1285
  });
991
1286
  console.log(topTable.toString());
992
1287
  console.log("");
993
1288
  }
994
- const edtoolsDir = path4.join(projectPath, ".edtools");
995
- await fs5.ensureDir(edtoolsDir);
996
- const opportunitiesPath = path4.join(edtoolsDir, "opportunities.json");
1289
+ const edtoolsDir = path5.join(projectPath, ".edtools");
1290
+ await fs6.ensureDir(edtoolsDir);
1291
+ const opportunitiesPath = path5.join(edtoolsDir, "opportunities.json");
997
1292
  await saveOpportunities(analysis.opportunities, opportunitiesPath);
998
- console.log(successBox(`Analysis saved to ${chalk4.white(".edtools/opportunities.json")}`));
999
- console.log(chalk4.cyan.bold("Next steps:"));
1000
- console.log(` ${chalk4.cyan("1.")} Generate content: ${chalk4.white("edtools generate --from-csv")}`);
1001
- console.log(` ${chalk4.cyan("2.")} Or specify topics: ${chalk4.white('edtools generate -t "topic 1" "topic 2"')}
1293
+ console.log(successBox(`Analysis saved to ${chalk5.white(".edtools/opportunities.json")}`));
1294
+ console.log(chalk5.cyan.bold("Next steps:"));
1295
+ console.log(` ${chalk5.cyan("1.")} Generate content: ${chalk5.white("edtools generate --from-csv")}`);
1296
+ console.log(` ${chalk5.cyan("2.")} Or specify topics: ${chalk5.white('edtools generate -t "topic 1" "topic 2"')}
1002
1297
  `);
1003
- console.log(chalk4.gray("\u{1F4A1} Use --from-csv to automatically generate posts for top opportunities\n"));
1298
+ console.log(chalk5.gray("\u{1F4A1} Use --from-csv to automatically generate posts for top opportunities\n"));
1004
1299
  }
1005
1300
 
1006
1301
  // src/cli/commands/validate.ts
1007
- import fs7 from "fs-extra";
1008
- import path5 from "path";
1009
- import chalk5 from "chalk";
1302
+ import fs8 from "fs-extra";
1303
+ import path6 from "path";
1304
+ import chalk6 from "chalk";
1010
1305
  import Table3 from "cli-table3";
1011
1306
 
1012
1307
  // src/utils/seo-validator.ts
1013
1308
  import * as cheerio from "cheerio";
1014
- import fs6 from "fs-extra";
1309
+ import fs7 from "fs-extra";
1015
1310
  async function validatePost(htmlPath) {
1016
- const html = await fs6.readFile(htmlPath, "utf-8");
1311
+ const html = await fs7.readFile(htmlPath, "utf-8");
1017
1312
  const $ = cheerio.load(html);
1018
1313
  const issues = [];
1019
1314
  const passed = [];
@@ -1236,19 +1531,19 @@ function calculateValidationStats(results) {
1236
1531
 
1237
1532
  // src/cli/commands/validate.ts
1238
1533
  async function validateCommand(options) {
1239
- console.log(chalk5.cyan.bold("\n\u{1F4CA} Validating SEO quality...\n"));
1534
+ console.log(chalk6.cyan.bold("\n\u{1F4CA} Validating SEO quality...\n"));
1240
1535
  const projectPath = process.cwd();
1241
1536
  let htmlFiles = [];
1242
1537
  if (options.post) {
1243
- const postPath = path5.resolve(projectPath, options.post);
1244
- if (!await fs7.pathExists(postPath)) {
1538
+ const postPath = path6.resolve(projectPath, options.post);
1539
+ if (!await fs8.pathExists(postPath)) {
1245
1540
  console.log(errorBox(`Post not found: ${postPath}`));
1246
1541
  process.exit(1);
1247
1542
  }
1248
1543
  htmlFiles = [postPath];
1249
1544
  } else if (options.posts) {
1250
- const postsDir = path5.resolve(projectPath, options.posts);
1251
- if (!await fs7.pathExists(postsDir)) {
1545
+ const postsDir = path6.resolve(projectPath, options.posts);
1546
+ if (!await fs8.pathExists(postsDir)) {
1252
1547
  console.log(errorBox(`Directory not found: ${postsDir}`));
1253
1548
  process.exit(1);
1254
1549
  }
@@ -1258,8 +1553,8 @@ async function validateCommand(options) {
1258
1553
  process.exit(1);
1259
1554
  }
1260
1555
  } else {
1261
- const defaultBlogDir = path5.join(projectPath, "blog");
1262
- if (await fs7.pathExists(defaultBlogDir)) {
1556
+ const defaultBlogDir = path6.join(projectPath, "blog");
1557
+ if (await fs8.pathExists(defaultBlogDir)) {
1263
1558
  htmlFiles = await findHtmlFiles(defaultBlogDir);
1264
1559
  if (htmlFiles.length === 0) {
1265
1560
  console.log(errorBox("No HTML files found in blog/ directory"));
@@ -1267,11 +1562,11 @@ async function validateCommand(options) {
1267
1562
  }
1268
1563
  } else {
1269
1564
  console.log(errorBox("No posts directory specified"));
1270
- console.log(chalk5.yellow("Usage: edtools validate --posts <dir> or --post <file>\n"));
1565
+ console.log(chalk6.yellow("Usage: edtools validate --posts <dir> or --post <file>\n"));
1271
1566
  process.exit(1);
1272
1567
  }
1273
1568
  }
1274
- console.log(chalk5.cyan(`Found ${htmlFiles.length} post${htmlFiles.length > 1 ? "s" : ""} to validate
1569
+ console.log(chalk6.cyan(`Found ${htmlFiles.length} post${htmlFiles.length > 1 ? "s" : ""} to validate
1275
1570
  `));
1276
1571
  const results = [];
1277
1572
  for (const htmlFile of htmlFiles) {
@@ -1279,7 +1574,7 @@ async function validateCommand(options) {
1279
1574
  const result = await validatePost(htmlFile);
1280
1575
  results.push(result);
1281
1576
  } catch (error) {
1282
- console.log(chalk5.yellow(`\u26A0\uFE0F Failed to validate ${htmlFile}: ${error.message}`));
1577
+ console.log(chalk6.yellow(`\u26A0\uFE0F Failed to validate ${htmlFile}: ${error.message}`));
1283
1578
  }
1284
1579
  }
1285
1580
  if (results.length === 0) {
@@ -1304,17 +1599,17 @@ async function validateCommand(options) {
1304
1599
  })),
1305
1600
  stats: stats2
1306
1601
  };
1307
- const outputPath = path5.resolve(projectPath, options.output);
1308
- await fs7.writeJson(outputPath, report, { spaces: 2 });
1602
+ const outputPath = path6.resolve(projectPath, options.output);
1603
+ await fs8.writeJson(outputPath, report, { spaces: 2 });
1309
1604
  console.log(successBox(`Validation report saved to ${outputPath}`));
1310
1605
  }
1311
1606
  if (filteredResults.length > 0) {
1312
1607
  const table = new Table3({
1313
1608
  head: [
1314
- chalk5.cyan.bold("#"),
1315
- chalk5.cyan.bold("Title"),
1316
- chalk5.cyan.bold("Score"),
1317
- chalk5.cyan.bold("Issues")
1609
+ chalk6.cyan.bold("#"),
1610
+ chalk6.cyan.bold("Title"),
1611
+ chalk6.cyan.bold("Score"),
1612
+ chalk6.cyan.bold("Issues")
1318
1613
  ],
1319
1614
  colWidths: [5, 40, 10, 50],
1320
1615
  wordWrap: true,
@@ -1327,12 +1622,12 @@ async function validateCommand(options) {
1327
1622
  const scoreColor = getScoreColor(result.seoScore);
1328
1623
  const issuesText = result.issues.length > 0 ? result.issues.map((issue) => {
1329
1624
  const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warning" ? "\u26A0" : "\u2139";
1330
- const color = issue.severity === "error" ? chalk5.red : issue.severity === "warning" ? chalk5.yellow : chalk5.blue;
1625
+ const color = issue.severity === "error" ? chalk6.red : issue.severity === "warning" ? chalk6.yellow : chalk6.blue;
1331
1626
  return color(`${icon} ${issue.message}`);
1332
- }).join("\n") : chalk5.green("\u2713 No issues");
1627
+ }).join("\n") : chalk6.green("\u2713 No issues");
1333
1628
  table.push([
1334
- chalk5.gray((i + 1).toString()),
1335
- chalk5.white(truncate(result.title, 38)),
1629
+ chalk6.gray((i + 1).toString()),
1630
+ chalk6.white(truncate(result.title, 38)),
1336
1631
  scoreColor(`${result.seoScore}/100`),
1337
1632
  issuesText
1338
1633
  ]);
@@ -1343,39 +1638,39 @@ async function validateCommand(options) {
1343
1638
  console.log(successBox(`All posts have SEO score >= ${threshold}`));
1344
1639
  }
1345
1640
  const stats = calculateValidationStats(results);
1346
- console.log(chalk5.cyan.bold("Overall Statistics:\n"));
1347
- console.log(` Total posts validated: ${chalk5.white(stats.totalPosts)}`);
1641
+ console.log(chalk6.cyan.bold("Overall Statistics:\n"));
1642
+ console.log(` Total posts validated: ${chalk6.white(stats.totalPosts)}`);
1348
1643
  console.log(` Average SEO score: ${getScoreColor(stats.avgSeoScore)(stats.avgSeoScore + "/100")}`);
1349
- console.log(` Posts with issues: ${chalk5.white(stats.postsWithIssues)} (${Math.round(stats.postsWithIssues / stats.totalPosts * 100)}%)`);
1350
- console.log(` Total issues found: ${chalk5.white(stats.totalIssues)}`);
1644
+ console.log(` Posts with issues: ${chalk6.white(stats.postsWithIssues)} (${Math.round(stats.postsWithIssues / stats.totalPosts * 100)}%)`);
1645
+ console.log(` Total issues found: ${chalk6.white(stats.totalIssues)}`);
1351
1646
  console.log("");
1352
1647
  if (stats.totalIssues > 0) {
1353
- console.log(chalk5.cyan.bold("Issues by Severity:\n"));
1648
+ console.log(chalk6.cyan.bold("Issues by Severity:\n"));
1354
1649
  if (stats.issuesBySeverity.error > 0) {
1355
- console.log(` ${chalk5.red("\u2717 Errors:")} ${chalk5.white(stats.issuesBySeverity.error)}`);
1650
+ console.log(` ${chalk6.red("\u2717 Errors:")} ${chalk6.white(stats.issuesBySeverity.error)}`);
1356
1651
  }
1357
1652
  if (stats.issuesBySeverity.warning > 0) {
1358
- console.log(` ${chalk5.yellow("\u26A0 Warnings:")} ${chalk5.white(stats.issuesBySeverity.warning)}`);
1653
+ console.log(` ${chalk6.yellow("\u26A0 Warnings:")} ${chalk6.white(stats.issuesBySeverity.warning)}`);
1359
1654
  }
1360
1655
  if (stats.issuesBySeverity.info > 0) {
1361
- console.log(` ${chalk5.blue("\u2139 Info:")} ${chalk5.white(stats.issuesBySeverity.info)}`);
1656
+ console.log(` ${chalk6.blue("\u2139 Info:")} ${chalk6.white(stats.issuesBySeverity.info)}`);
1362
1657
  }
1363
1658
  console.log("");
1364
1659
  }
1365
1660
  if (Object.keys(stats.issuesByCategory).length > 0) {
1366
- console.log(chalk5.cyan.bold("Top Issue Categories:\n"));
1661
+ console.log(chalk6.cyan.bold("Top Issue Categories:\n"));
1367
1662
  const sortedCategories = Object.entries(stats.issuesByCategory).sort((a, b) => b[1] - a[1]).slice(0, 5);
1368
1663
  sortedCategories.forEach(([category, count]) => {
1369
- console.log(` ${chalk5.white(capitalize(category))}: ${chalk5.yellow(count)}`);
1664
+ console.log(` ${chalk6.white(capitalize(category))}: ${chalk6.yellow(count)}`);
1370
1665
  });
1371
1666
  console.log("");
1372
1667
  }
1373
1668
  if (stats.avgSeoScore < 85) {
1374
- console.log(chalk5.yellow.bold("\u{1F4A1} Recommendations:\n"));
1375
- console.log(chalk5.yellow(" \u2022 Focus on fixing critical errors first"));
1376
- console.log(chalk5.yellow(" \u2022 Optimize meta descriptions to 150-160 characters"));
1377
- console.log(chalk5.yellow(" \u2022 Ensure all posts have proper heading structure (H1, H2s)"));
1378
- console.log(chalk5.yellow(" \u2022 Add alt text to all images"));
1669
+ console.log(chalk6.yellow.bold("\u{1F4A1} Recommendations:\n"));
1670
+ console.log(chalk6.yellow(" \u2022 Focus on fixing critical errors first"));
1671
+ console.log(chalk6.yellow(" \u2022 Optimize meta descriptions to 150-160 characters"));
1672
+ console.log(chalk6.yellow(" \u2022 Ensure all posts have proper heading structure (H1, H2s)"));
1673
+ console.log(chalk6.yellow(" \u2022 Add alt text to all images"));
1379
1674
  console.log("");
1380
1675
  } else {
1381
1676
  console.log(successBox(`Great job! Average SEO score is ${stats.avgSeoScore}/100`));
@@ -1383,9 +1678,9 @@ async function validateCommand(options) {
1383
1678
  }
1384
1679
  async function findHtmlFiles(dir) {
1385
1680
  const files = [];
1386
- const entries = await fs7.readdir(dir, { withFileTypes: true });
1681
+ const entries = await fs8.readdir(dir, { withFileTypes: true });
1387
1682
  for (const entry of entries) {
1388
- const fullPath = path5.join(dir, entry.name);
1683
+ const fullPath = path6.join(dir, entry.name);
1389
1684
  if (entry.isDirectory()) {
1390
1685
  const subFiles = await findHtmlFiles(fullPath);
1391
1686
  files.push(...subFiles);
@@ -1396,10 +1691,10 @@ async function findHtmlFiles(dir) {
1396
1691
  return files;
1397
1692
  }
1398
1693
  function getScoreColor(score) {
1399
- if (score >= 90) return chalk5.green;
1400
- if (score >= 75) return chalk5.yellow;
1401
- if (score >= 60) return chalk5.hex("#FFA500");
1402
- return chalk5.red;
1694
+ if (score >= 90) return chalk6.green;
1695
+ if (score >= 75) return chalk6.yellow;
1696
+ if (score >= 60) return chalk6.hex("#FFA500");
1697
+ return chalk6.red;
1403
1698
  }
1404
1699
  function truncate(str, maxLen) {
1405
1700
  if (str.length <= maxLen) return str;
@@ -1410,22 +1705,22 @@ function capitalize(str) {
1410
1705
  }
1411
1706
 
1412
1707
  // src/cli/commands/doctor.ts
1413
- import fs8 from "fs-extra";
1414
- import path6 from "path";
1708
+ import fs9 from "fs-extra";
1709
+ import path7 from "path";
1415
1710
  import { pathToFileURL as pathToFileURL2 } from "url";
1416
- import chalk6 from "chalk";
1711
+ import chalk7 from "chalk";
1417
1712
  async function doctorCommand(options) {
1418
- console.log(chalk6.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
1713
+ console.log(chalk7.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
1419
1714
  const projectPath = process.cwd();
1420
- const configPath = path6.join(projectPath, "edtools.config.js");
1715
+ const configPath = path7.join(projectPath, "edtools.config.js");
1421
1716
  const issues = [];
1422
1717
  const warnings = [];
1423
1718
  const suggestions = [];
1424
- if (await fs8.pathExists(configPath)) {
1425
- console.log(chalk6.green("\u2713 Configuration file found: ") + chalk6.white("edtools.config.js"));
1719
+ if (await fs9.pathExists(configPath)) {
1720
+ console.log(chalk7.green("\u2713 Configuration file found: ") + chalk7.white("edtools.config.js"));
1426
1721
  } else {
1427
- console.log(chalk6.red("\u2717 Configuration file not found"));
1428
- console.log(chalk6.yellow(' Run "edtools init" to create configuration\n'));
1722
+ console.log(chalk7.red("\u2717 Configuration file not found"));
1723
+ console.log(chalk7.yellow(' Run "edtools init" to create configuration\n'));
1429
1724
  process.exit(1);
1430
1725
  }
1431
1726
  let productInfo;
@@ -1440,115 +1735,115 @@ async function doctorCommand(options) {
1440
1735
  }
1441
1736
  } catch (error) {
1442
1737
  issues.push(`Failed to load edtools.config.js: ${error.message}`);
1443
- console.log(chalk6.red("\u2717 Failed to load configuration file"));
1444
- console.log(chalk6.red(` Error: ${error.message}
1738
+ console.log(chalk7.red("\u2717 Failed to load configuration file"));
1739
+ console.log(chalk7.red(` Error: ${error.message}
1445
1740
  `));
1446
1741
  process.exit(1);
1447
1742
  }
1448
- const outputDirPath = path6.resolve(projectPath, outputDir);
1449
- if (await fs8.pathExists(outputDirPath)) {
1450
- console.log(chalk6.green("\u2713 Output directory exists: ") + chalk6.white(outputDir));
1743
+ const outputDirPath = path7.resolve(projectPath, outputDir);
1744
+ if (await fs9.pathExists(outputDirPath)) {
1745
+ console.log(chalk7.green("\u2713 Output directory exists: ") + chalk7.white(outputDir));
1451
1746
  } else {
1452
1747
  warnings.push(`Output directory does not exist: ${outputDir}`);
1453
- console.log(chalk6.yellow("\u26A0\uFE0F Output directory does not exist: ") + chalk6.white(outputDir));
1454
- console.log(chalk6.yellow(' It will be created when you run "edtools generate"'));
1748
+ console.log(chalk7.yellow("\u26A0\uFE0F Output directory does not exist: ") + chalk7.white(outputDir));
1749
+ console.log(chalk7.yellow(' It will be created when you run "edtools generate"'));
1455
1750
  }
1456
- const edtoolsConfigPath = path6.join(projectPath, ".edtools", "config.json");
1751
+ const edtoolsConfigPath = path7.join(projectPath, ".edtools", "config.json");
1457
1752
  let hasApiKey = false;
1458
- if (await fs8.pathExists(edtoolsConfigPath)) {
1753
+ if (await fs9.pathExists(edtoolsConfigPath)) {
1459
1754
  try {
1460
- const edtoolsConfig = await fs8.readJson(edtoolsConfigPath);
1755
+ const edtoolsConfig = await fs9.readJson(edtoolsConfigPath);
1461
1756
  hasApiKey = !!edtoolsConfig.apiKey;
1462
1757
  } catch (error) {
1463
1758
  }
1464
1759
  }
1465
1760
  const hasEnvApiKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
1466
1761
  if (hasApiKey || hasEnvApiKey) {
1467
- console.log(chalk6.green("\u2713 API key configured"));
1762
+ console.log(chalk7.green("\u2713 API key configured"));
1468
1763
  } else {
1469
1764
  warnings.push('No API key found (set via environment variable or "edtools init")');
1470
- console.log(chalk6.yellow("\u26A0\uFE0F No API key configured"));
1471
- console.log(chalk6.yellow(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
1472
- console.log(chalk6.yellow(' Or run "edtools init" to store API key'));
1765
+ console.log(chalk7.yellow("\u26A0\uFE0F No API key configured"));
1766
+ console.log(chalk7.yellow(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
1767
+ console.log(chalk7.yellow(' Or run "edtools init" to store API key'));
1473
1768
  }
1474
1769
  console.log("");
1475
1770
  const hostingConfig = detectHostingConfig(projectPath);
1476
1771
  if (hostingConfig) {
1477
- console.log(chalk6.cyan("\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk6.white(hostingConfig.platform));
1478
- console.log(chalk6.cyan(" - Public directory: ") + chalk6.white(hostingConfig.publicDir));
1479
- console.log(chalk6.cyan(" - Your outputDir: ") + chalk6.white(outputDir));
1772
+ console.log(chalk7.cyan("\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk7.white(hostingConfig.platform));
1773
+ console.log(chalk7.cyan(" - Public directory: ") + chalk7.white(hostingConfig.publicDir));
1774
+ console.log(chalk7.cyan(" - Your outputDir: ") + chalk7.white(outputDir));
1480
1775
  const validation = validateOutputDir(outputDir, hostingConfig);
1481
1776
  if (!validation.valid) {
1482
1777
  issues.push("Output directory misconfiguration");
1483
- console.log(chalk6.red(" - Problem: Files outside ") + chalk6.white(hostingConfig.publicDir + "/") + chalk6.red(" won't be accessible"));
1778
+ console.log(chalk7.red(" - Problem: Files outside ") + chalk7.white(hostingConfig.publicDir + "/") + chalk7.red(" won't be accessible"));
1484
1779
  console.log("");
1485
- console.log(chalk6.red.bold("\u274C ISSUE: Output directory misconfiguration"));
1486
- console.log(chalk6.red(" Generated files will return 404 in production"));
1780
+ console.log(chalk7.red.bold("\u274C ISSUE: Output directory misconfiguration"));
1781
+ console.log(chalk7.red(" Generated files will return 404 in production"));
1487
1782
  console.log("");
1488
- console.log(chalk6.cyan.bold("\u{1F4DD} Suggested fix:"));
1489
- console.log(chalk6.cyan(" Update edtools.config.js:"));
1783
+ console.log(chalk7.cyan.bold("\u{1F4DD} Suggested fix:"));
1784
+ console.log(chalk7.cyan(" Update edtools.config.js:"));
1490
1785
  console.log("");
1491
- console.log(chalk6.gray(" content: {"));
1492
- console.log(chalk6.green(` outputDir: '${validation.suggestion}'`) + chalk6.gray(" // \u2705 Correct path"));
1493
- console.log(chalk6.gray(" }"));
1786
+ console.log(chalk7.gray(" content: {"));
1787
+ console.log(chalk7.green(` outputDir: '${validation.suggestion}'`) + chalk7.gray(" // \u2705 Correct path"));
1788
+ console.log(chalk7.gray(" }"));
1494
1789
  console.log("");
1495
1790
  suggestions.push(`Update outputDir to '${validation.suggestion}'`);
1496
1791
  if (options.fix) {
1497
- console.log(chalk6.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1792
+ console.log(chalk7.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1498
1793
  try {
1499
- const configContent = await fs8.readFile(configPath, "utf-8");
1794
+ const configContent = await fs9.readFile(configPath, "utf-8");
1500
1795
  const updatedConfig = configContent.replace(
1501
1796
  /outputDir:\s*['"]([^'"]+)['"]/,
1502
1797
  `outputDir: '${validation.suggestion}'`
1503
1798
  );
1504
- await fs8.writeFile(configPath, updatedConfig, "utf-8");
1505
- console.log(chalk6.green("\u2713 Updated edtools.config.js"));
1506
- console.log(chalk6.green(` outputDir: '${validation.suggestion}'`));
1799
+ await fs9.writeFile(configPath, updatedConfig, "utf-8");
1800
+ console.log(chalk7.green("\u2713 Updated edtools.config.js"));
1801
+ console.log(chalk7.green(` outputDir: '${validation.suggestion}'`));
1507
1802
  console.log("");
1508
- console.log(chalk6.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
1803
+ console.log(chalk7.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
1509
1804
  } catch (error) {
1510
- console.log(chalk6.red("\u2717 Failed to auto-fix configuration"));
1511
- console.log(chalk6.red(` Error: ${error.message}`));
1512
- console.log(chalk6.yellow(" Please update edtools.config.js manually\n"));
1805
+ console.log(chalk7.red("\u2717 Failed to auto-fix configuration"));
1806
+ console.log(chalk7.red(` Error: ${error.message}`));
1807
+ console.log(chalk7.yellow(" Please update edtools.config.js manually\n"));
1513
1808
  }
1514
1809
  } else {
1515
- console.log(chalk6.cyan.bold("Run: ") + chalk6.white("edtools doctor --fix"));
1516
- console.log(chalk6.cyan("To automatically apply suggested fixes\n"));
1810
+ console.log(chalk7.cyan.bold("Run: ") + chalk7.white("edtools doctor --fix"));
1811
+ console.log(chalk7.cyan("To automatically apply suggested fixes\n"));
1517
1812
  }
1518
1813
  } else {
1519
- console.log(chalk6.green(" - Status: \u2705 Output directory correctly configured"));
1814
+ console.log(chalk7.green(" - Status: \u2705 Output directory correctly configured"));
1520
1815
  console.log("");
1521
1816
  }
1522
1817
  } else {
1523
- console.log(chalk6.gray("No hosting platform configuration detected"));
1524
- console.log(chalk6.gray("(No firebase.json, vercel.json, netlify.toml, or amplify.yml found)"));
1818
+ console.log(chalk7.gray("No hosting platform configuration detected"));
1819
+ console.log(chalk7.gray("(No firebase.json, vercel.json, netlify.toml, or amplify.yml found)"));
1525
1820
  console.log("");
1526
1821
  }
1527
- console.log(chalk6.cyan.bold("Summary:\n"));
1822
+ console.log(chalk7.cyan.bold("Summary:\n"));
1528
1823
  if (issues.length === 0 && warnings.length === 0) {
1529
- console.log(chalk6.green.bold("\u2705 All checks passed! Your project is ready to generate content.\n"));
1824
+ console.log(chalk7.green.bold("\u2705 All checks passed! Your project is ready to generate content.\n"));
1530
1825
  } else {
1531
1826
  if (issues.length > 0) {
1532
- console.log(chalk6.red.bold(`\u274C ${issues.length} issue(s) found:`));
1827
+ console.log(chalk7.red.bold(`\u274C ${issues.length} issue(s) found:`));
1533
1828
  issues.forEach((issue) => {
1534
- console.log(chalk6.red(` - ${issue}`));
1829
+ console.log(chalk7.red(` - ${issue}`));
1535
1830
  });
1536
1831
  console.log("");
1537
1832
  }
1538
1833
  if (warnings.length > 0) {
1539
- console.log(chalk6.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1834
+ console.log(chalk7.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1540
1835
  warnings.forEach((warning) => {
1541
- console.log(chalk6.yellow(` - ${warning}`));
1836
+ console.log(chalk7.yellow(` - ${warning}`));
1542
1837
  });
1543
1838
  console.log("");
1544
1839
  }
1545
1840
  if (suggestions.length > 0 && !options.fix) {
1546
- console.log(chalk6.cyan.bold("\u{1F4A1} Suggestions:"));
1841
+ console.log(chalk7.cyan.bold("\u{1F4A1} Suggestions:"));
1547
1842
  suggestions.forEach((suggestion) => {
1548
- console.log(chalk6.cyan(` - ${suggestion}`));
1843
+ console.log(chalk7.cyan(` - ${suggestion}`));
1549
1844
  });
1550
1845
  console.log("");
1551
- console.log(chalk6.cyan("Run ") + chalk6.white("edtools doctor --fix") + chalk6.cyan(" to automatically apply fixes\n"));
1846
+ console.log(chalk7.cyan("Run ") + chalk7.white("edtools doctor --fix") + chalk7.cyan(" to automatically apply fixes\n"));
1552
1847
  }
1553
1848
  }
1554
1849
  }
@@ -1561,14 +1856,9 @@ program.command("generate").description("Generate SEO-optimized blog posts").opt
1561
1856
  program.command("analyze").description("Analyze Google Search Console CSV data").option("-f, --file <path>", "Path to CSV file (auto-detects if not provided)").option("--min-impressions <number>", "Minimum impressions for opportunities", "50").option("--min-position <number>", "Minimum position for opportunities", "20").option("--limit <number>", "Number of opportunities to show", "10").action(analyzeCommand);
1562
1857
  program.command("validate").description("Validate SEO quality of existing posts").option("--posts <dir>", "Posts directory to validate").option("--post <file>", "Single post file to validate").option("--output <file>", "Save validation report as JSON").option("--threshold <score>", "Only show posts below this score").action(validateCommand);
1563
1858
  program.command("doctor").description("Diagnose project configuration and hosting setup").option("--fix", "Automatically fix issues").action(doctorCommand);
1564
- program.command("config").description("View or set configuration").option("--set-api-key <key>", "Set Anthropic API key").action(async (options) => {
1565
- if (options.setApiKey) {
1566
- console.log(chalk7.green("\u2713 API key saved"));
1567
- } else {
1568
- console.log(chalk7.cyan("Configuration:"));
1569
- console.log(` API Key: ${process.env.ANTHROPIC_API_KEY ? "[set]" : "[not set]"}`);
1570
- }
1571
- });
1859
+ var configCmd = program.command("config").description("Manage edtools configuration");
1860
+ configCmd.command("show").description("Show current configuration status").option("-p, --path <path>", "Project path", process.cwd()).action(configShowCommand);
1861
+ configCmd.command("set-api-key").description("Set or update API key without overwriting edtools.config.js").option("-p, --path <path>", "Project path", process.cwd()).option("-k, --key <key>", "API key (if not provided, will prompt)").option("--provider <provider>", "Provider: anthropic or openai").action(configSetApiKeyCommand);
1572
1862
  if (process.argv.length === 2) {
1573
1863
  showWelcome();
1574
1864
  process.exit(0);