@edtools/cli 0.6.1 → 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 (39) 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 +1 -1
  20. package/dist/cli/commands/generate.d.ts.map +1 -1
  21. package/dist/cli/commands/generate.js +74 -17
  22. package/dist/cli/commands/generate.js.map +1 -1
  23. package/dist/cli/commands/init.d.ts.map +1 -1
  24. package/dist/cli/commands/init.js +114 -0
  25. package/dist/cli/commands/init.js.map +1 -1
  26. package/dist/cli/index.js +567 -265
  27. package/dist/cli/index.js.map +1 -1
  28. package/dist/core/generator.d.ts +4 -1
  29. package/dist/core/generator.d.ts.map +1 -1
  30. package/dist/core/generator.js +114 -3
  31. package/dist/core/generator.js.map +1 -1
  32. package/dist/index.d.ts +87 -3
  33. package/dist/index.js +1 -1
  34. package/dist/types/adapter.d.ts +17 -2
  35. package/dist/types/adapter.d.ts.map +1 -1
  36. package/dist/types/adapter.js.map +1 -1
  37. package/dist/types/content.d.ts +66 -0
  38. package/dist/types/content.d.ts.map +1 -1
  39. 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,131 +573,322 @@ ${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"));
837
+ }
838
+ let outputDirConfig;
839
+ let outputDirSource;
840
+ if (options.output) {
841
+ outputDirConfig = options.output;
842
+ outputDirSource = "CLI flag (--output)";
843
+ } else if (config.default.content?.outputDir) {
844
+ outputDirConfig = config.default.content.outputDir;
845
+ outputDirSource = "edtools.config.js";
846
+ } else {
847
+ outputDirConfig = "./blog";
848
+ outputDirSource = "default";
544
849
  }
545
- const outputDir = path3.resolve(projectPath, options.output);
546
- await fs3.ensureDir(outputDir);
850
+ const outputDir = path4.resolve(projectPath, outputDirConfig);
851
+ await fs4.ensureDir(outputDir);
547
852
  const hostingConfig = detectHostingConfig(projectPath);
548
853
  if (hostingConfig) {
549
- const validation = validateOutputDir(options.output, hostingConfig);
854
+ const validation = validateOutputDir(outputDirConfig, hostingConfig);
550
855
  if (!validation.valid) {
551
- console.log(chalk3.yellow.bold("\n\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk3.white(validation.platform));
552
- console.log(chalk3.yellow(" - Public directory: ") + chalk3.white(validation.publicDir));
553
- console.log(chalk3.yellow(" - Your outputDir: ") + chalk3.white(validation.currentOutputDir));
554
- 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"));
555
860
  console.log("");
556
- console.log(chalk3.red.bold("\u274C ISSUE: Output directory misconfiguration"));
557
- 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"));
558
863
  console.log("");
559
- console.log(chalk3.cyan.bold("\u{1F4DD} Suggested fix:"));
560
- 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:"));
561
866
  console.log("");
562
- console.log(chalk3.gray(" content: {"));
563
- console.log(chalk3.green(` outputDir: '${validation.suggestion}'`) + chalk3.gray(" // \u2705 Correct path"));
564
- 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(" }"));
565
870
  console.log("");
566
- const answer = await inquirer2.prompt([{
871
+ const answer = await inquirer3.prompt([{
567
872
  type: "confirm",
568
873
  name: "continue",
569
874
  message: "Continue anyway?",
570
875
  default: false
571
876
  }]);
572
877
  if (!answer.continue) {
573
- console.log(chalk3.yellow("\n\u2717 Generation cancelled\n"));
878
+ console.log(chalk4.yellow("\n\u2717 Generation cancelled\n"));
574
879
  process.exit(0);
575
880
  }
576
881
  console.log("");
577
882
  }
578
883
  }
579
- console.log(chalk3.cyan("Configuration:"));
580
- console.log(` Product: ${chalk3.white(productInfo.name)}`);
581
- console.log(` Category: ${chalk3.white(productInfo.category)}`);
582
- console.log(` AI Provider: ${chalk3.white(provider === "anthropic" ? "Claude (Anthropic)" : "ChatGPT (OpenAI)")}`);
583
- console.log(` Posts to generate: ${chalk3.white(count)}`);
584
- console.log(` Output directory: ${chalk3.white(outputDir)}
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 + ")")}
585
890
  `);
586
- const generator = new ContentGenerator(apiKey, provider);
891
+ const generator = new ContentGenerator(apiKey, actualProvider);
587
892
  const generateConfig = {
588
893
  productInfo,
589
894
  topics,
@@ -592,20 +897,22 @@ async function generateCommand(options) {
592
897
  projectPath,
593
898
  avoidDuplicates: true,
594
899
  similarityThreshold: 0.85,
595
- provider,
900
+ provider: actualProvider,
596
901
  apiKey,
597
- dryRun: options.dryRun
902
+ dryRun: options.dryRun,
903
+ blogConfig: config.default.blog
904
+ // Pass blog config from edtools.config.js
598
905
  };
599
- const providerName = provider === "anthropic" ? "Claude" : "ChatGPT";
906
+ const providerName = actualProvider === "anthropic" ? "Claude" : "ChatGPT";
600
907
  const spinner = ora2(`Generating content with ${providerName}...`).start();
601
908
  try {
602
909
  const result = await generator.generate(generateConfig);
603
910
  if (result.success && result.posts.length > 0) {
604
911
  const dryRunMode = result.dryRun || false;
605
- spinner.succeed(chalk3.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
912
+ spinner.succeed(chalk4.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
606
913
  console.log("");
607
914
  if (dryRunMode) {
608
- 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"));
609
916
  }
610
917
  if (options.report === "json" || options.report === "pretty") {
611
918
  const report = {
@@ -644,11 +951,11 @@ async function generateCommand(options) {
644
951
  } else {
645
952
  const table = new Table({
646
953
  head: [
647
- chalk3.cyan.bold("#"),
648
- chalk3.cyan.bold("Title"),
649
- chalk3.cyan.bold("Words"),
650
- chalk3.cyan.bold("SEO"),
651
- 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")
652
959
  ],
653
960
  colWidths: [5, 35, 10, 10, 45],
654
961
  wordWrap: true,
@@ -660,29 +967,29 @@ async function generateCommand(options) {
660
967
  result.posts.forEach((post, i) => {
661
968
  const scoreColor = getScoreColorFn(post.seoScore);
662
969
  table.push([
663
- chalk3.gray((i + 1).toString()),
664
- chalk3.white(truncateTitle(post.title, 33)),
665
- chalk3.white(post.wordCount.toString()),
970
+ chalk4.gray((i + 1).toString()),
971
+ chalk4.white(truncateTitle(post.title, 33)),
972
+ chalk4.white(post.wordCount.toString()),
666
973
  scoreColor(`${post.seoScore}/100`),
667
- chalk3.gray(post.path)
974
+ chalk4.gray(post.path)
668
975
  ]);
669
976
  });
670
977
  console.log(table.toString());
671
978
  console.log("");
672
979
  if (result.stats) {
673
- console.log(chalk3.cyan.bold("Statistics:\n"));
674
- 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)}`);
675
982
  console.log(` Avg SEO score: ${getScoreColorFn(result.stats.avgSeoScore)(result.stats.avgSeoScore + "/100")}`);
676
- console.log(` Avg read time: ${chalk3.white(result.stats.avgReadTime)}`);
983
+ console.log(` Avg read time: ${chalk4.white(result.stats.avgReadTime)}`);
677
984
  console.log("");
678
985
  }
679
986
  if (dryRunMode) {
680
- console.log(chalk3.yellow.bold("Would create:\n"));
987
+ console.log(chalk4.yellow.bold("Would create:\n"));
681
988
  if (result.manifestPath) {
682
- 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)`);
683
990
  }
684
991
  if (result.sitemapPath) {
685
- 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)`);
686
993
  }
687
994
  console.log("");
688
995
  }
@@ -694,21 +1001,21 @@ async function generateCommand(options) {
694
1001
  console.log(warningBox(warningText));
695
1002
  }
696
1003
  if (dryRunMode) {
697
- console.log(chalk3.cyan.bold("Next step:"));
698
- 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
699
1006
  `);
700
1007
  } else {
701
- console.log(chalk3.cyan.bold("Next steps:"));
702
- console.log(` ${chalk3.cyan("1.")} Review generated content in ${chalk3.white(outputDir)}`);
703
- console.log(` ${chalk3.cyan("2.")} Edit posts to add personal experience/expertise`);
704
- 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`);
705
1012
  console.log("");
706
- 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
707
1014
  `));
708
1015
  }
709
1016
  }
710
1017
  } else {
711
- spinner.fail(chalk3.bold("Failed to generate content"));
1018
+ spinner.fail(chalk4.bold("Failed to generate content"));
712
1019
  if (result.errors && result.errors.length > 0) {
713
1020
  const errorText = result.errors.join("\n");
714
1021
  console.log(errorBox(errorText));
@@ -716,16 +1023,16 @@ async function generateCommand(options) {
716
1023
  process.exit(1);
717
1024
  }
718
1025
  } catch (error) {
719
- spinner.fail(chalk3.bold("Error during generation"));
1026
+ spinner.fail(chalk4.bold("Error during generation"));
720
1027
  console.log(errorBox(error.message));
721
1028
  process.exit(1);
722
1029
  }
723
1030
  }
724
1031
  function getScoreColorFn(score) {
725
- if (score >= 90) return chalk3.green;
726
- if (score >= 75) return chalk3.yellow;
727
- if (score >= 60) return chalk3.hex("#FFA500");
728
- 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;
729
1036
  }
730
1037
  function truncateTitle(title, maxLen) {
731
1038
  if (title.length <= maxLen) return title;
@@ -733,17 +1040,17 @@ function truncateTitle(title, maxLen) {
733
1040
  }
734
1041
 
735
1042
  // src/cli/commands/analyze.ts
736
- import fs5 from "fs-extra";
737
- import path4 from "path";
738
- import chalk4 from "chalk";
1043
+ import fs6 from "fs-extra";
1044
+ import path5 from "path";
1045
+ import chalk5 from "chalk";
739
1046
  import ora3 from "ora";
740
1047
  import Table2 from "cli-table3";
741
1048
 
742
1049
  // src/integrations/gsc-csv-analyzer.ts
743
- import fs4 from "fs-extra";
1050
+ import fs5 from "fs-extra";
744
1051
  import { parse } from "csv-parse/sync";
745
1052
  async function parseGSCCSV(filePath) {
746
- const content = await fs4.readFile(filePath, "utf-8");
1053
+ const content = await fs5.readFile(filePath, "utf-8");
747
1054
  const records = parse(content, {
748
1055
  columns: true,
749
1056
  skip_empty_lines: true,
@@ -839,34 +1146,34 @@ async function saveOpportunities(opportunities, outputPath) {
839
1146
  count: opportunities.length,
840
1147
  opportunities
841
1148
  };
842
- await fs4.writeJson(outputPath, data, { spaces: 2 });
1149
+ await fs5.writeJson(outputPath, data, { spaces: 2 });
843
1150
  }
844
1151
 
845
1152
  // src/cli/commands/analyze.ts
846
1153
  async function analyzeCommand(options) {
847
- 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"));
848
1155
  const projectPath = process.cwd();
849
1156
  let csvPath;
850
1157
  if (options.file) {
851
- csvPath = path4.resolve(options.file);
1158
+ csvPath = path5.resolve(options.file);
852
1159
  } else {
853
- const files = await fs5.readdir(projectPath);
1160
+ const files = await fs6.readdir(projectPath);
854
1161
  const csvFiles = files.filter((f) => f.endsWith(".csv") && f.toLowerCase().includes("search"));
855
1162
  if (csvFiles.length === 0) {
856
- console.log(chalk4.red("\u2717 No CSV file found"));
857
- console.log(chalk4.yellow(" Please provide a CSV file using --file option"));
858
- 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"));
859
1166
  process.exit(1);
860
1167
  }
861
1168
  if (csvFiles.length > 1) {
862
- console.log(chalk4.yellow("\u26A0 Multiple CSV files found:"));
1169
+ console.log(chalk5.yellow("\u26A0 Multiple CSV files found:"));
863
1170
  csvFiles.forEach((f) => console.log(` - ${f}`));
864
- 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"));
865
1172
  }
866
- csvPath = path4.join(projectPath, csvFiles[0]);
1173
+ csvPath = path5.join(projectPath, csvFiles[0]);
867
1174
  }
868
- if (!await fs5.pathExists(csvPath)) {
869
- 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}
870
1177
  `));
871
1178
  process.exit(1);
872
1179
  }
@@ -874,10 +1181,10 @@ async function analyzeCommand(options) {
874
1181
  let data;
875
1182
  try {
876
1183
  data = await parseGSCCSV(csvPath);
877
- 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))}`);
878
1185
  } catch (error) {
879
1186
  spinner.fail("Failed to parse CSV");
880
- console.log(chalk4.red(`
1187
+ console.log(chalk5.red(`
881
1188
  Error: ${error.message}
882
1189
  `));
883
1190
  process.exit(1);
@@ -892,7 +1199,7 @@ Error: ${error.message}
892
1199
  analyzeSpinner.succeed("Analysis complete!");
893
1200
  console.log("");
894
1201
  const metricsTable = new Table2({
895
- head: [chalk4.cyan.bold("Metric"), chalk4.cyan.bold("Value")],
1202
+ head: [chalk5.cyan.bold("Metric"), chalk5.cyan.bold("Value")],
896
1203
  colWidths: [30, 20],
897
1204
  style: {
898
1205
  head: [],
@@ -900,28 +1207,28 @@ Error: ${error.message}
900
1207
  }
901
1208
  });
902
1209
  metricsTable.push(
903
- ["Total Keywords", chalk4.white(analysis.totalQueries.toLocaleString())],
904
- ["Total Impressions", chalk4.white(analysis.totalImpressions.toLocaleString())],
905
- ["Total Clicks", chalk4.white(analysis.totalClicks.toLocaleString())],
906
- ["Average CTR", chalk4.white((analysis.avgCTR * 100).toFixed(2) + "%")],
907
- ["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))]
908
1215
  );
909
- console.log(chalk4.bold.cyan("\u{1F4C8} Overall Metrics\n"));
1216
+ console.log(chalk5.bold.cyan("\u{1F4C8} Overall Metrics\n"));
910
1217
  console.log(metricsTable.toString());
911
1218
  console.log("");
912
1219
  if (analysis.opportunities.length === 0) {
913
1220
  console.log(warningBox("No opportunities found with current criteria.\nTry lowering --min-impressions or increasing --min-position."));
914
1221
  } else {
915
- 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
916
1223
  `));
917
1224
  const oppTable = new Table2({
918
1225
  head: [
919
- chalk4.cyan.bold("#"),
920
- chalk4.cyan.bold("Keyword"),
921
- chalk4.cyan.bold("Impressions"),
922
- chalk4.cyan.bold("Position"),
923
- chalk4.cyan.bold("Potential"),
924
- 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")
925
1232
  ],
926
1233
  colWidths: [5, 40, 14, 12, 12, 12],
927
1234
  wordWrap: true,
@@ -931,34 +1238,34 @@ Error: ${error.message}
931
1238
  }
932
1239
  });
933
1240
  analysis.opportunities.forEach((opp, i) => {
934
- 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;
935
1242
  oppTable.push([
936
- chalk4.gray((i + 1).toString()),
937
- chalk4.white(opp.keyword),
938
- chalk4.cyan(opp.impressions.toLocaleString()),
939
- chalk4.yellow(opp.position.toFixed(1)),
940
- 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}`),
941
1248
  priorityColor(opp.priority.toUpperCase())
942
1249
  ]);
943
1250
  });
944
1251
  console.log(oppTable.toString());
945
1252
  console.log("");
946
- 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"));
947
1254
  analysis.opportunities.slice(0, 5).forEach((opp, i) => {
948
- console.log(` ${chalk4.cyan((i + 1).toString() + ".")} ${chalk4.white(opp.suggestedTitle)}`);
949
- 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)}
950
1257
  `);
951
1258
  });
952
1259
  }
953
1260
  if (analysis.topKeywords.length > 0) {
954
- 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"));
955
1262
  const topTable = new Table2({
956
1263
  head: [
957
- chalk4.cyan.bold("#"),
958
- chalk4.cyan.bold("Keyword"),
959
- chalk4.cyan.bold("Clicks"),
960
- chalk4.cyan.bold("Impressions"),
961
- 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")
962
1269
  ],
963
1270
  colWidths: [5, 40, 12, 14, 12],
964
1271
  wordWrap: true,
@@ -969,39 +1276,39 @@ Error: ${error.message}
969
1276
  });
970
1277
  analysis.topKeywords.slice(0, 5).forEach((kw, i) => {
971
1278
  topTable.push([
972
- chalk4.gray((i + 1).toString()),
973
- chalk4.white(kw.query),
974
- chalk4.green(kw.clicks.toString()),
975
- chalk4.cyan(kw.impressions.toLocaleString()),
976
- 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) + "%")
977
1284
  ]);
978
1285
  });
979
1286
  console.log(topTable.toString());
980
1287
  console.log("");
981
1288
  }
982
- const edtoolsDir = path4.join(projectPath, ".edtools");
983
- await fs5.ensureDir(edtoolsDir);
984
- 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");
985
1292
  await saveOpportunities(analysis.opportunities, opportunitiesPath);
986
- console.log(successBox(`Analysis saved to ${chalk4.white(".edtools/opportunities.json")}`));
987
- console.log(chalk4.cyan.bold("Next steps:"));
988
- console.log(` ${chalk4.cyan("1.")} Generate content: ${chalk4.white("edtools generate --from-csv")}`);
989
- 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"')}
990
1297
  `);
991
- 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"));
992
1299
  }
993
1300
 
994
1301
  // src/cli/commands/validate.ts
995
- import fs7 from "fs-extra";
996
- import path5 from "path";
997
- import chalk5 from "chalk";
1302
+ import fs8 from "fs-extra";
1303
+ import path6 from "path";
1304
+ import chalk6 from "chalk";
998
1305
  import Table3 from "cli-table3";
999
1306
 
1000
1307
  // src/utils/seo-validator.ts
1001
1308
  import * as cheerio from "cheerio";
1002
- import fs6 from "fs-extra";
1309
+ import fs7 from "fs-extra";
1003
1310
  async function validatePost(htmlPath) {
1004
- const html = await fs6.readFile(htmlPath, "utf-8");
1311
+ const html = await fs7.readFile(htmlPath, "utf-8");
1005
1312
  const $ = cheerio.load(html);
1006
1313
  const issues = [];
1007
1314
  const passed = [];
@@ -1224,19 +1531,19 @@ function calculateValidationStats(results) {
1224
1531
 
1225
1532
  // src/cli/commands/validate.ts
1226
1533
  async function validateCommand(options) {
1227
- 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"));
1228
1535
  const projectPath = process.cwd();
1229
1536
  let htmlFiles = [];
1230
1537
  if (options.post) {
1231
- const postPath = path5.resolve(projectPath, options.post);
1232
- if (!await fs7.pathExists(postPath)) {
1538
+ const postPath = path6.resolve(projectPath, options.post);
1539
+ if (!await fs8.pathExists(postPath)) {
1233
1540
  console.log(errorBox(`Post not found: ${postPath}`));
1234
1541
  process.exit(1);
1235
1542
  }
1236
1543
  htmlFiles = [postPath];
1237
1544
  } else if (options.posts) {
1238
- const postsDir = path5.resolve(projectPath, options.posts);
1239
- if (!await fs7.pathExists(postsDir)) {
1545
+ const postsDir = path6.resolve(projectPath, options.posts);
1546
+ if (!await fs8.pathExists(postsDir)) {
1240
1547
  console.log(errorBox(`Directory not found: ${postsDir}`));
1241
1548
  process.exit(1);
1242
1549
  }
@@ -1246,8 +1553,8 @@ async function validateCommand(options) {
1246
1553
  process.exit(1);
1247
1554
  }
1248
1555
  } else {
1249
- const defaultBlogDir = path5.join(projectPath, "blog");
1250
- if (await fs7.pathExists(defaultBlogDir)) {
1556
+ const defaultBlogDir = path6.join(projectPath, "blog");
1557
+ if (await fs8.pathExists(defaultBlogDir)) {
1251
1558
  htmlFiles = await findHtmlFiles(defaultBlogDir);
1252
1559
  if (htmlFiles.length === 0) {
1253
1560
  console.log(errorBox("No HTML files found in blog/ directory"));
@@ -1255,11 +1562,11 @@ async function validateCommand(options) {
1255
1562
  }
1256
1563
  } else {
1257
1564
  console.log(errorBox("No posts directory specified"));
1258
- 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"));
1259
1566
  process.exit(1);
1260
1567
  }
1261
1568
  }
1262
- 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
1263
1570
  `));
1264
1571
  const results = [];
1265
1572
  for (const htmlFile of htmlFiles) {
@@ -1267,7 +1574,7 @@ async function validateCommand(options) {
1267
1574
  const result = await validatePost(htmlFile);
1268
1575
  results.push(result);
1269
1576
  } catch (error) {
1270
- 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}`));
1271
1578
  }
1272
1579
  }
1273
1580
  if (results.length === 0) {
@@ -1292,17 +1599,17 @@ async function validateCommand(options) {
1292
1599
  })),
1293
1600
  stats: stats2
1294
1601
  };
1295
- const outputPath = path5.resolve(projectPath, options.output);
1296
- await fs7.writeJson(outputPath, report, { spaces: 2 });
1602
+ const outputPath = path6.resolve(projectPath, options.output);
1603
+ await fs8.writeJson(outputPath, report, { spaces: 2 });
1297
1604
  console.log(successBox(`Validation report saved to ${outputPath}`));
1298
1605
  }
1299
1606
  if (filteredResults.length > 0) {
1300
1607
  const table = new Table3({
1301
1608
  head: [
1302
- chalk5.cyan.bold("#"),
1303
- chalk5.cyan.bold("Title"),
1304
- chalk5.cyan.bold("Score"),
1305
- chalk5.cyan.bold("Issues")
1609
+ chalk6.cyan.bold("#"),
1610
+ chalk6.cyan.bold("Title"),
1611
+ chalk6.cyan.bold("Score"),
1612
+ chalk6.cyan.bold("Issues")
1306
1613
  ],
1307
1614
  colWidths: [5, 40, 10, 50],
1308
1615
  wordWrap: true,
@@ -1315,12 +1622,12 @@ async function validateCommand(options) {
1315
1622
  const scoreColor = getScoreColor(result.seoScore);
1316
1623
  const issuesText = result.issues.length > 0 ? result.issues.map((issue) => {
1317
1624
  const icon = issue.severity === "error" ? "\u2717" : issue.severity === "warning" ? "\u26A0" : "\u2139";
1318
- 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;
1319
1626
  return color(`${icon} ${issue.message}`);
1320
- }).join("\n") : chalk5.green("\u2713 No issues");
1627
+ }).join("\n") : chalk6.green("\u2713 No issues");
1321
1628
  table.push([
1322
- chalk5.gray((i + 1).toString()),
1323
- chalk5.white(truncate(result.title, 38)),
1629
+ chalk6.gray((i + 1).toString()),
1630
+ chalk6.white(truncate(result.title, 38)),
1324
1631
  scoreColor(`${result.seoScore}/100`),
1325
1632
  issuesText
1326
1633
  ]);
@@ -1331,39 +1638,39 @@ async function validateCommand(options) {
1331
1638
  console.log(successBox(`All posts have SEO score >= ${threshold}`));
1332
1639
  }
1333
1640
  const stats = calculateValidationStats(results);
1334
- console.log(chalk5.cyan.bold("Overall Statistics:\n"));
1335
- 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)}`);
1336
1643
  console.log(` Average SEO score: ${getScoreColor(stats.avgSeoScore)(stats.avgSeoScore + "/100")}`);
1337
- console.log(` Posts with issues: ${chalk5.white(stats.postsWithIssues)} (${Math.round(stats.postsWithIssues / stats.totalPosts * 100)}%)`);
1338
- 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)}`);
1339
1646
  console.log("");
1340
1647
  if (stats.totalIssues > 0) {
1341
- console.log(chalk5.cyan.bold("Issues by Severity:\n"));
1648
+ console.log(chalk6.cyan.bold("Issues by Severity:\n"));
1342
1649
  if (stats.issuesBySeverity.error > 0) {
1343
- console.log(` ${chalk5.red("\u2717 Errors:")} ${chalk5.white(stats.issuesBySeverity.error)}`);
1650
+ console.log(` ${chalk6.red("\u2717 Errors:")} ${chalk6.white(stats.issuesBySeverity.error)}`);
1344
1651
  }
1345
1652
  if (stats.issuesBySeverity.warning > 0) {
1346
- console.log(` ${chalk5.yellow("\u26A0 Warnings:")} ${chalk5.white(stats.issuesBySeverity.warning)}`);
1653
+ console.log(` ${chalk6.yellow("\u26A0 Warnings:")} ${chalk6.white(stats.issuesBySeverity.warning)}`);
1347
1654
  }
1348
1655
  if (stats.issuesBySeverity.info > 0) {
1349
- console.log(` ${chalk5.blue("\u2139 Info:")} ${chalk5.white(stats.issuesBySeverity.info)}`);
1656
+ console.log(` ${chalk6.blue("\u2139 Info:")} ${chalk6.white(stats.issuesBySeverity.info)}`);
1350
1657
  }
1351
1658
  console.log("");
1352
1659
  }
1353
1660
  if (Object.keys(stats.issuesByCategory).length > 0) {
1354
- console.log(chalk5.cyan.bold("Top Issue Categories:\n"));
1661
+ console.log(chalk6.cyan.bold("Top Issue Categories:\n"));
1355
1662
  const sortedCategories = Object.entries(stats.issuesByCategory).sort((a, b) => b[1] - a[1]).slice(0, 5);
1356
1663
  sortedCategories.forEach(([category, count]) => {
1357
- console.log(` ${chalk5.white(capitalize(category))}: ${chalk5.yellow(count)}`);
1664
+ console.log(` ${chalk6.white(capitalize(category))}: ${chalk6.yellow(count)}`);
1358
1665
  });
1359
1666
  console.log("");
1360
1667
  }
1361
1668
  if (stats.avgSeoScore < 85) {
1362
- console.log(chalk5.yellow.bold("\u{1F4A1} Recommendations:\n"));
1363
- console.log(chalk5.yellow(" \u2022 Focus on fixing critical errors first"));
1364
- console.log(chalk5.yellow(" \u2022 Optimize meta descriptions to 150-160 characters"));
1365
- console.log(chalk5.yellow(" \u2022 Ensure all posts have proper heading structure (H1, H2s)"));
1366
- 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"));
1367
1674
  console.log("");
1368
1675
  } else {
1369
1676
  console.log(successBox(`Great job! Average SEO score is ${stats.avgSeoScore}/100`));
@@ -1371,9 +1678,9 @@ async function validateCommand(options) {
1371
1678
  }
1372
1679
  async function findHtmlFiles(dir) {
1373
1680
  const files = [];
1374
- const entries = await fs7.readdir(dir, { withFileTypes: true });
1681
+ const entries = await fs8.readdir(dir, { withFileTypes: true });
1375
1682
  for (const entry of entries) {
1376
- const fullPath = path5.join(dir, entry.name);
1683
+ const fullPath = path6.join(dir, entry.name);
1377
1684
  if (entry.isDirectory()) {
1378
1685
  const subFiles = await findHtmlFiles(fullPath);
1379
1686
  files.push(...subFiles);
@@ -1384,10 +1691,10 @@ async function findHtmlFiles(dir) {
1384
1691
  return files;
1385
1692
  }
1386
1693
  function getScoreColor(score) {
1387
- if (score >= 90) return chalk5.green;
1388
- if (score >= 75) return chalk5.yellow;
1389
- if (score >= 60) return chalk5.hex("#FFA500");
1390
- 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;
1391
1698
  }
1392
1699
  function truncate(str, maxLen) {
1393
1700
  if (str.length <= maxLen) return str;
@@ -1398,22 +1705,22 @@ function capitalize(str) {
1398
1705
  }
1399
1706
 
1400
1707
  // src/cli/commands/doctor.ts
1401
- import fs8 from "fs-extra";
1402
- import path6 from "path";
1708
+ import fs9 from "fs-extra";
1709
+ import path7 from "path";
1403
1710
  import { pathToFileURL as pathToFileURL2 } from "url";
1404
- import chalk6 from "chalk";
1711
+ import chalk7 from "chalk";
1405
1712
  async function doctorCommand(options) {
1406
- 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"));
1407
1714
  const projectPath = process.cwd();
1408
- const configPath = path6.join(projectPath, "edtools.config.js");
1715
+ const configPath = path7.join(projectPath, "edtools.config.js");
1409
1716
  const issues = [];
1410
1717
  const warnings = [];
1411
1718
  const suggestions = [];
1412
- if (await fs8.pathExists(configPath)) {
1413
- 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"));
1414
1721
  } else {
1415
- console.log(chalk6.red("\u2717 Configuration file not found"));
1416
- 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'));
1417
1724
  process.exit(1);
1418
1725
  }
1419
1726
  let productInfo;
@@ -1428,135 +1735,130 @@ async function doctorCommand(options) {
1428
1735
  }
1429
1736
  } catch (error) {
1430
1737
  issues.push(`Failed to load edtools.config.js: ${error.message}`);
1431
- console.log(chalk6.red("\u2717 Failed to load configuration file"));
1432
- 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}
1433
1740
  `));
1434
1741
  process.exit(1);
1435
1742
  }
1436
- const outputDirPath = path6.resolve(projectPath, outputDir);
1437
- if (await fs8.pathExists(outputDirPath)) {
1438
- 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));
1439
1746
  } else {
1440
1747
  warnings.push(`Output directory does not exist: ${outputDir}`);
1441
- console.log(chalk6.yellow("\u26A0\uFE0F Output directory does not exist: ") + chalk6.white(outputDir));
1442
- 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"'));
1443
1750
  }
1444
- const edtoolsConfigPath = path6.join(projectPath, ".edtools", "config.json");
1751
+ const edtoolsConfigPath = path7.join(projectPath, ".edtools", "config.json");
1445
1752
  let hasApiKey = false;
1446
- if (await fs8.pathExists(edtoolsConfigPath)) {
1753
+ if (await fs9.pathExists(edtoolsConfigPath)) {
1447
1754
  try {
1448
- const edtoolsConfig = await fs8.readJson(edtoolsConfigPath);
1755
+ const edtoolsConfig = await fs9.readJson(edtoolsConfigPath);
1449
1756
  hasApiKey = !!edtoolsConfig.apiKey;
1450
1757
  } catch (error) {
1451
1758
  }
1452
1759
  }
1453
1760
  const hasEnvApiKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
1454
1761
  if (hasApiKey || hasEnvApiKey) {
1455
- console.log(chalk6.green("\u2713 API key configured"));
1762
+ console.log(chalk7.green("\u2713 API key configured"));
1456
1763
  } else {
1457
1764
  warnings.push('No API key found (set via environment variable or "edtools init")');
1458
- console.log(chalk6.yellow("\u26A0\uFE0F No API key configured"));
1459
- console.log(chalk6.yellow(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
1460
- 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'));
1461
1768
  }
1462
1769
  console.log("");
1463
1770
  const hostingConfig = detectHostingConfig(projectPath);
1464
1771
  if (hostingConfig) {
1465
- console.log(chalk6.cyan("\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk6.white(hostingConfig.platform));
1466
- console.log(chalk6.cyan(" - Public directory: ") + chalk6.white(hostingConfig.publicDir));
1467
- 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));
1468
1775
  const validation = validateOutputDir(outputDir, hostingConfig);
1469
1776
  if (!validation.valid) {
1470
1777
  issues.push("Output directory misconfiguration");
1471
- 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"));
1472
1779
  console.log("");
1473
- console.log(chalk6.red.bold("\u274C ISSUE: Output directory misconfiguration"));
1474
- 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"));
1475
1782
  console.log("");
1476
- console.log(chalk6.cyan.bold("\u{1F4DD} Suggested fix:"));
1477
- 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:"));
1478
1785
  console.log("");
1479
- console.log(chalk6.gray(" content: {"));
1480
- console.log(chalk6.green(` outputDir: '${validation.suggestion}'`) + chalk6.gray(" // \u2705 Correct path"));
1481
- 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(" }"));
1482
1789
  console.log("");
1483
1790
  suggestions.push(`Update outputDir to '${validation.suggestion}'`);
1484
1791
  if (options.fix) {
1485
- console.log(chalk6.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1792
+ console.log(chalk7.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1486
1793
  try {
1487
- const configContent = await fs8.readFile(configPath, "utf-8");
1794
+ const configContent = await fs9.readFile(configPath, "utf-8");
1488
1795
  const updatedConfig = configContent.replace(
1489
1796
  /outputDir:\s*['"]([^'"]+)['"]/,
1490
1797
  `outputDir: '${validation.suggestion}'`
1491
1798
  );
1492
- await fs8.writeFile(configPath, updatedConfig, "utf-8");
1493
- console.log(chalk6.green("\u2713 Updated edtools.config.js"));
1494
- 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}'`));
1495
1802
  console.log("");
1496
- 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'));
1497
1804
  } catch (error) {
1498
- console.log(chalk6.red("\u2717 Failed to auto-fix configuration"));
1499
- console.log(chalk6.red(` Error: ${error.message}`));
1500
- 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"));
1501
1808
  }
1502
1809
  } else {
1503
- console.log(chalk6.cyan.bold("Run: ") + chalk6.white("edtools doctor --fix"));
1504
- 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"));
1505
1812
  }
1506
1813
  } else {
1507
- console.log(chalk6.green(" - Status: \u2705 Output directory correctly configured"));
1814
+ console.log(chalk7.green(" - Status: \u2705 Output directory correctly configured"));
1508
1815
  console.log("");
1509
1816
  }
1510
1817
  } else {
1511
- console.log(chalk6.gray("No hosting platform configuration detected"));
1512
- 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)"));
1513
1820
  console.log("");
1514
1821
  }
1515
- console.log(chalk6.cyan.bold("Summary:\n"));
1822
+ console.log(chalk7.cyan.bold("Summary:\n"));
1516
1823
  if (issues.length === 0 && warnings.length === 0) {
1517
- 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"));
1518
1825
  } else {
1519
1826
  if (issues.length > 0) {
1520
- console.log(chalk6.red.bold(`\u274C ${issues.length} issue(s) found:`));
1827
+ console.log(chalk7.red.bold(`\u274C ${issues.length} issue(s) found:`));
1521
1828
  issues.forEach((issue) => {
1522
- console.log(chalk6.red(` - ${issue}`));
1829
+ console.log(chalk7.red(` - ${issue}`));
1523
1830
  });
1524
1831
  console.log("");
1525
1832
  }
1526
1833
  if (warnings.length > 0) {
1527
- console.log(chalk6.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1834
+ console.log(chalk7.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1528
1835
  warnings.forEach((warning) => {
1529
- console.log(chalk6.yellow(` - ${warning}`));
1836
+ console.log(chalk7.yellow(` - ${warning}`));
1530
1837
  });
1531
1838
  console.log("");
1532
1839
  }
1533
1840
  if (suggestions.length > 0 && !options.fix) {
1534
- console.log(chalk6.cyan.bold("\u{1F4A1} Suggestions:"));
1841
+ console.log(chalk7.cyan.bold("\u{1F4A1} Suggestions:"));
1535
1842
  suggestions.forEach((suggestion) => {
1536
- console.log(chalk6.cyan(` - ${suggestion}`));
1843
+ console.log(chalk7.cyan(` - ${suggestion}`));
1537
1844
  });
1538
1845
  console.log("");
1539
- 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"));
1540
1847
  }
1541
1848
  }
1542
1849
  }
1543
1850
 
1544
1851
  // src/cli/index.ts
1545
1852
  var program = new Command();
1546
- program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.1");
1853
+ program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.2");
1547
1854
  program.command("init").description("Initialize edtools in your project").option("-p, --path <path>", "Project path", process.cwd()).action(initCommand);
1548
- program.command("generate").description("Generate SEO-optimized blog posts").option("-n, --posts <number>", "Number of posts to generate (1-10)", "3").option("-t, --topics <topics...>", "Specific topics to write about").option("-o, --output <dir>", "Output directory", "./blog").option("--api-key <key>", "API key (or use ANTHROPIC_API_KEY/OPENAI_API_KEY env var)").option("--from-csv", "Generate from CSV analysis opportunities").option("--dry-run", "Preview what would be generated without writing files").option("--report <format>", "Output format: json, table, pretty (default: table)", "table").action(generateCommand);
1855
+ program.command("generate").description("Generate SEO-optimized blog posts").option("-n, --posts <number>", "Number of posts to generate (1-10)", "3").option("-t, --topics <topics...>", "Specific topics to write about").option("-o, --output <dir>", "Output directory (defaults to config or ./blog)").option("--api-key <key>", "API key (or use ANTHROPIC_API_KEY/OPENAI_API_KEY env var)").option("--from-csv", "Generate from CSV analysis opportunities").option("--dry-run", "Preview what would be generated without writing files").option("--report <format>", "Output format: json, table, pretty (default: table)", "table").action(generateCommand);
1549
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);
1550
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);
1551
1858
  program.command("doctor").description("Diagnose project configuration and hosting setup").option("--fix", "Automatically fix issues").action(doctorCommand);
1552
- program.command("config").description("View or set configuration").option("--set-api-key <key>", "Set Anthropic API key").action(async (options) => {
1553
- if (options.setApiKey) {
1554
- console.log(chalk7.green("\u2713 API key saved"));
1555
- } else {
1556
- console.log(chalk7.cyan("Configuration:"));
1557
- console.log(` API Key: ${process.env.ANTHROPIC_API_KEY ? "[set]" : "[not set]"}`);
1558
- }
1559
- });
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);
1560
1862
  if (process.argv.length === 2) {
1561
1863
  showWelcome();
1562
1864
  process.exit(0);