@edtools/cli 0.6.2 → 0.7.1

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 (43) 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 +39 -5
  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-EPM74QNH.js +844 -0
  14. package/dist/chunk-OVFZRN7O.js +850 -0
  15. package/dist/chunk-TROAGFSZ.js +824 -0
  16. package/dist/chunk-U77FH5BI.js +823 -0
  17. package/dist/cli/commands/config.d.ts +17 -0
  18. package/dist/cli/commands/config.d.ts.map +1 -0
  19. package/dist/cli/commands/config.js +140 -0
  20. package/dist/cli/commands/config.js.map +1 -0
  21. package/dist/cli/commands/generate.d.ts.map +1 -1
  22. package/dist/cli/commands/generate.js +57 -14
  23. package/dist/cli/commands/generate.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts.map +1 -1
  25. package/dist/cli/commands/init.js +114 -0
  26. package/dist/cli/commands/init.js.map +1 -1
  27. package/dist/cli/index.js +623 -288
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/core/generator.d.ts +4 -1
  30. package/dist/core/generator.d.ts.map +1 -1
  31. package/dist/core/generator.js +118 -3
  32. package/dist/core/generator.js.map +1 -1
  33. package/dist/index.d.ts +87 -3
  34. package/dist/index.js +1 -1
  35. package/dist/types/adapter.d.ts +17 -2
  36. package/dist/types/adapter.d.ts.map +1 -1
  37. package/dist/types/adapter.js.map +1 -1
  38. package/dist/types/content.d.ts +66 -0
  39. package/dist/types/content.d.ts.map +1 -1
  40. package/dist/ui/banner.d.ts.map +1 -1
  41. package/dist/ui/banner.js +18 -2
  42. package/dist/ui/banner.js.map +1 -1
  43. package/package.json +2 -2
package/dist/cli/index.js CHANGED
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ContentGenerator
4
- } from "../chunk-BI3UJPWA.js";
4
+ } from "../chunk-OVFZRN7O.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";
9
+ import fs11 from "fs-extra";
10
+ import path9 from "path";
11
+ import { fileURLToPath as fileURLToPath2 } from "url";
10
12
 
11
13
  // src/cli/commands/init.ts
12
- import fs2 from "fs-extra";
13
- import path2 from "path";
14
+ import fs3 from "fs-extra";
15
+ import path3 from "path";
14
16
  import chalk2 from "chalk";
15
17
  import ora from "ora";
16
18
  import inquirer from "inquirer";
@@ -21,6 +23,9 @@ import figlet from "figlet";
21
23
  import gradient from "gradient-string";
22
24
  import boxen from "boxen";
23
25
  import chalk from "chalk";
26
+ import fs from "fs-extra";
27
+ import path from "path";
28
+ import { fileURLToPath } from "url";
24
29
  function generateBanner() {
25
30
  const asciiArt = figlet.textSync("edtools", {
26
31
  font: "ANSI Shadow",
@@ -64,8 +69,24 @@ function showWelcome() {
64
69
  }
65
70
  function getVersion() {
66
71
  try {
67
- return "0.6.0";
68
- } catch {
72
+ const __filename = fileURLToPath(import.meta.url);
73
+ const __dirname = path.dirname(__filename);
74
+ const possiblePaths = [
75
+ path.join(__dirname, "../../package.json"),
76
+ // From dist/ui/
77
+ path.join(__dirname, "../../../package.json"),
78
+ // From src/ui/
79
+ path.join(__dirname, "../../../../package.json")
80
+ // From npm global install
81
+ ];
82
+ for (const pkgPath of possiblePaths) {
83
+ if (fs.existsSync(pkgPath)) {
84
+ const pkg = fs.readJsonSync(pkgPath);
85
+ return pkg.version || "unknown";
86
+ }
87
+ }
88
+ return "unknown";
89
+ } catch (error) {
69
90
  return "dev";
70
91
  }
71
92
  }
@@ -105,8 +126,8 @@ function warningBox(message) {
105
126
  }
106
127
 
107
128
  // src/utils/hosting-detection.ts
108
- import fs from "fs";
109
- import path from "path";
129
+ import fs2 from "fs";
130
+ import path2 from "path";
110
131
  import yaml from "yaml";
111
132
  var detectors = [
112
133
  {
@@ -182,9 +203,9 @@ var detectors = [
182
203
  file: "next.config.js",
183
204
  parse: (content, projectPath) => {
184
205
  if (content.includes("output:") && content.includes("export")) {
185
- const packageJsonPath = path.join(projectPath, "package.json");
186
- if (fs.existsSync(packageJsonPath)) {
187
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
206
+ const packageJsonPath = path2.join(projectPath, "package.json");
207
+ if (fs2.existsSync(packageJsonPath)) {
208
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
188
209
  const buildScript = packageJson.scripts?.build || "";
189
210
  if (buildScript.includes("out")) {
190
211
  return {
@@ -206,10 +227,10 @@ var detectors = [
206
227
  ];
207
228
  function detectHostingConfig(projectPath) {
208
229
  for (const detector of detectors) {
209
- const configPath = path.join(projectPath, detector.file);
210
- if (fs.existsSync(configPath)) {
230
+ const configPath = path2.join(projectPath, detector.file);
231
+ if (fs2.existsSync(configPath)) {
211
232
  try {
212
- const content = fs.readFileSync(configPath, "utf-8");
233
+ const content = fs2.readFileSync(configPath, "utf-8");
213
234
  const result = detector.parse(content, projectPath);
214
235
  if (result) {
215
236
  return result;
@@ -255,9 +276,9 @@ function getSuggestedOutputDir(projectPath) {
255
276
  // src/cli/commands/init.ts
256
277
  async function initCommand(options) {
257
278
  console.log(chalk2.cyan.bold("\n\u{1F680} Initializing Edtools...\n"));
258
- const projectPath = path2.resolve(options.path);
259
- const configPath = path2.join(projectPath, "edtools.config.js");
260
- if (await fs2.pathExists(configPath)) {
279
+ const projectPath = path3.resolve(options.path);
280
+ const configPath = path3.join(projectPath, "edtools.config.js");
281
+ if (await fs3.pathExists(configPath)) {
261
282
  const { overwrite } = await inquirer.prompt([
262
283
  {
263
284
  type: "confirm",
@@ -274,9 +295,9 @@ async function initCommand(options) {
274
295
  const spinner = ora("Analyzing your landing page...").start();
275
296
  let productInfo = {};
276
297
  try {
277
- const indexPath = path2.join(projectPath, "index.html");
278
- if (await fs2.pathExists(indexPath)) {
279
- const html = await fs2.readFile(indexPath, "utf-8");
298
+ const indexPath = path3.join(projectPath, "index.html");
299
+ if (await fs3.pathExists(indexPath)) {
300
+ const html = await fs3.readFile(indexPath, "utf-8");
280
301
  const $ = loadCheerio(html);
281
302
  productInfo = {
282
303
  name: $("title").text() || $("h1").first().text() || "My Product",
@@ -372,6 +393,111 @@ async function initCommand(options) {
372
393
  default: "anthropic"
373
394
  }
374
395
  ]);
396
+ console.log(chalk2.cyan("\n\u{1F3A8} Blog Branding (optional - press Enter to skip):\n"));
397
+ const blogBrandingAnswers = await inquirer.prompt([
398
+ {
399
+ type: "input",
400
+ name: "blogTitle",
401
+ message: "Blog title:",
402
+ default: `${answers.name} Blog`
403
+ },
404
+ {
405
+ type: "input",
406
+ name: "blogDescription",
407
+ message: "Blog description:",
408
+ default: `Learn about ${answers.name} and best practices`
409
+ },
410
+ {
411
+ type: "input",
412
+ name: "language",
413
+ message: "Blog language (en, es, fr, etc.):",
414
+ default: "en"
415
+ },
416
+ {
417
+ type: "input",
418
+ name: "logo",
419
+ message: "Logo URL (optional):",
420
+ default: ""
421
+ },
422
+ {
423
+ type: "input",
424
+ name: "primaryColor",
425
+ message: "Primary color (hex):",
426
+ default: "#2563eb",
427
+ validate: (input) => {
428
+ if (!input) return true;
429
+ return /^#[0-9A-F]{6}$/i.test(input) || "Please enter a valid hex color (e.g., #2563eb)";
430
+ }
431
+ }
432
+ ]);
433
+ const { addNavigation } = await inquirer.prompt([
434
+ {
435
+ type: "confirm",
436
+ name: "addNavigation",
437
+ message: "Add navigation links?",
438
+ default: false
439
+ }
440
+ ]);
441
+ let navigation = [];
442
+ if (addNavigation) {
443
+ let addMore = true;
444
+ while (addMore) {
445
+ const navItem = await inquirer.prompt([
446
+ {
447
+ type: "input",
448
+ name: "label",
449
+ message: "Navigation link label:",
450
+ validate: (input) => input.length > 0 || "Label is required"
451
+ },
452
+ {
453
+ type: "input",
454
+ name: "url",
455
+ message: "Navigation link URL:",
456
+ validate: (input) => input.length > 0 || "URL is required"
457
+ }
458
+ ]);
459
+ navigation.push(navItem);
460
+ const { continueAdding } = await inquirer.prompt([
461
+ {
462
+ type: "confirm",
463
+ name: "continueAdding",
464
+ message: "Add another navigation link?",
465
+ default: false
466
+ }
467
+ ]);
468
+ addMore = continueAdding;
469
+ }
470
+ }
471
+ console.log(chalk2.cyan("\n\u{1F4F1} Social Media (optional):\n"));
472
+ const socialAnswers = await inquirer.prompt([
473
+ {
474
+ type: "input",
475
+ name: "twitter",
476
+ message: "Twitter/X URL:",
477
+ default: ""
478
+ },
479
+ {
480
+ type: "input",
481
+ name: "linkedin",
482
+ message: "LinkedIn URL:",
483
+ default: ""
484
+ },
485
+ {
486
+ type: "input",
487
+ name: "github",
488
+ message: "GitHub URL:",
489
+ default: ""
490
+ },
491
+ {
492
+ type: "input",
493
+ name: "youtube",
494
+ message: "YouTube URL:",
495
+ default: ""
496
+ }
497
+ ]);
498
+ const social = Object.fromEntries(
499
+ Object.entries(socialAnswers).filter(([_, value]) => value !== "")
500
+ );
375
501
  console.log(chalk2.cyan("\n\u{1F511} API Key Setup:\n"));
376
502
  const apiKeyAnswer = await inquirer.prompt([
377
503
  {
@@ -417,6 +543,16 @@ async function initCommand(options) {
417
543
  preferredProvider: ${JSON.stringify(finalProductInfo.preferredProvider)},
418
544
  },
419
545
 
546
+ blog: {
547
+ title: ${JSON.stringify(blogBrandingAnswers.blogTitle)},
548
+ description: ${JSON.stringify(blogBrandingAnswers.blogDescription)},
549
+ language: ${JSON.stringify(blogBrandingAnswers.language)},
550
+ logo: ${JSON.stringify(blogBrandingAnswers.logo)},
551
+ primaryColor: ${JSON.stringify(blogBrandingAnswers.primaryColor)},
552
+ navigation: ${JSON.stringify(navigation, null, 2)},
553
+ social: ${JSON.stringify(social, null, 2)},
554
+ },
555
+
420
556
  content: {
421
557
  outputDir: ${JSON.stringify(suggestedOutputDir)},
422
558
  generateBlog: true,
@@ -429,17 +565,17 @@ async function initCommand(options) {
429
565
  },
430
566
  };
431
567
  `;
432
- await fs2.writeFile(configPath, config, "utf-8");
433
- const edtoolsDir = path2.join(projectPath, ".edtools");
434
- await fs2.ensureDir(edtoolsDir);
568
+ await fs3.writeFile(configPath, config, "utf-8");
569
+ const edtoolsDir = path3.join(projectPath, ".edtools");
570
+ await fs3.ensureDir(edtoolsDir);
435
571
  const edtoolsConfig = {
436
572
  apiKey: apiKeyAnswer.apiKey,
437
573
  provider: finalProductInfo.preferredProvider
438
574
  };
439
- const edtoolsConfigPath = path2.join(edtoolsDir, "config.json");
440
- await fs2.writeFile(edtoolsConfigPath, JSON.stringify(edtoolsConfig, null, 2), "utf-8");
441
- const gitignorePath = path2.join(edtoolsDir, ".gitignore");
442
- await fs2.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
575
+ const edtoolsConfigPath = path3.join(edtoolsDir, "config.json");
576
+ await fs3.writeFile(edtoolsConfigPath, JSON.stringify(edtoolsConfig, null, 2), "utf-8");
577
+ const gitignorePath = path3.join(edtoolsDir, ".gitignore");
578
+ await fs3.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
443
579
  console.log("");
444
580
  console.log(successBox("Configuration created successfully!"));
445
581
  const filesCreated = `${chalk2.cyan("Files created:")}
@@ -459,88 +595,267 @@ ${chalk2.gray("API key stored securely (gitignored)")}`;
459
595
  }
460
596
 
461
597
  // src/cli/commands/generate.ts
462
- import fs3 from "fs-extra";
463
- import path3 from "path";
598
+ import fs5 from "fs-extra";
599
+ import path5 from "path";
464
600
  import { pathToFileURL } from "url";
465
- import chalk3 from "chalk";
601
+ import chalk4 from "chalk";
466
602
  import ora2 from "ora";
467
603
  import Table from "cli-table3";
604
+ import inquirer3 from "inquirer";
605
+
606
+ // src/cli/commands/config.ts
607
+ import fs4 from "fs-extra";
608
+ import path4 from "path";
609
+ import chalk3 from "chalk";
468
610
  import inquirer2 from "inquirer";
611
+ async function promptForApiKey(projectPath, providedKey, providedProvider) {
612
+ const edtoolsDir = path4.join(projectPath, ".edtools");
613
+ const configPath = path4.join(edtoolsDir, "config.json");
614
+ await fs4.ensureDir(edtoolsDir);
615
+ let existingConfig = {};
616
+ if (await fs4.pathExists(configPath)) {
617
+ try {
618
+ existingConfig = await fs4.readJson(configPath);
619
+ } catch (error) {
620
+ console.log(chalk3.yellow("\u26A0\uFE0F Existing config is corrupted, will create new one"));
621
+ }
622
+ }
623
+ let apiKey = providedKey;
624
+ let provider = providedProvider || existingConfig.provider || "anthropic";
625
+ if (!apiKey) {
626
+ console.log(chalk3.cyan("\n\u{1F511} API Key Setup:\n"));
627
+ const providerAnswer = await inquirer2.prompt([
628
+ {
629
+ type: "list",
630
+ name: "provider",
631
+ message: "AI provider:",
632
+ choices: [
633
+ { name: "Claude (Anthropic) - Recommended", value: "anthropic" },
634
+ { name: "ChatGPT (OpenAI)", value: "openai" }
635
+ ],
636
+ default: provider
637
+ }
638
+ ]);
639
+ provider = providerAnswer.provider;
640
+ const keyAnswer = await inquirer2.prompt([
641
+ {
642
+ type: "password",
643
+ name: "apiKey",
644
+ message: provider === "anthropic" ? "Anthropic API Key (from https://console.anthropic.com):" : "OpenAI API Key (from https://platform.openai.com/api-keys):",
645
+ validate: (input) => {
646
+ if (!input || input.trim().length === 0) {
647
+ return "API key is required";
648
+ }
649
+ if (provider === "anthropic" && !input.startsWith("sk-ant-")) {
650
+ return 'Anthropic API keys should start with "sk-ant-"';
651
+ }
652
+ if (provider === "openai" && !input.startsWith("sk-")) {
653
+ return 'OpenAI API keys should start with "sk-"';
654
+ }
655
+ return true;
656
+ }
657
+ }
658
+ ]);
659
+ apiKey = keyAnswer.apiKey;
660
+ }
661
+ if (provider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
662
+ console.log(errorBox('Invalid API Key: Anthropic API keys should start with "sk-ant-"'));
663
+ return null;
664
+ }
665
+ if (provider === "openai" && !apiKey.startsWith("sk-")) {
666
+ console.log(errorBox('Invalid API Key: OpenAI API keys should start with "sk-"'));
667
+ return null;
668
+ }
669
+ const config = {
670
+ ...existingConfig,
671
+ apiKey,
672
+ provider
673
+ };
674
+ await fs4.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
675
+ const gitignorePath = path4.join(edtoolsDir, ".gitignore");
676
+ if (!await fs4.pathExists(gitignorePath)) {
677
+ await fs4.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
678
+ }
679
+ console.log("");
680
+ console.log(successBox(`API Key configured successfully for ${provider}!`));
681
+ console.log(chalk3.gray(`Stored in: ${chalk3.white(".edtools/config.json")} (gitignored)
682
+ `));
683
+ return { apiKey, provider };
684
+ }
685
+ async function configSetApiKeyCommand(options) {
686
+ const projectPath = path4.resolve(options.path);
687
+ const result = await promptForApiKey(projectPath, options.key, options.provider);
688
+ if (!result) {
689
+ console.log(chalk3.red("\n\u2717 API key setup cancelled or failed\n"));
690
+ process.exit(1);
691
+ }
692
+ }
693
+ async function configShowCommand(options) {
694
+ const projectPath = path4.resolve(options.path);
695
+ const edtoolsConfigPath = path4.join(projectPath, ".edtools", "config.json");
696
+ const mainConfigPath = path4.join(projectPath, "edtools.config.js");
697
+ console.log(chalk3.cyan.bold("\n\u{1F4CB} Edtools Configuration:\n"));
698
+ if (await fs4.pathExists(mainConfigPath)) {
699
+ console.log(chalk3.green("\u2713") + " edtools.config.js found");
700
+ console.log(chalk3.gray(` Location: ${mainConfigPath}
701
+ `));
702
+ } else {
703
+ console.log(chalk3.red("\u2717") + " edtools.config.js not found");
704
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools init")} to create it
705
+ `));
706
+ }
707
+ if (await fs4.pathExists(edtoolsConfigPath)) {
708
+ try {
709
+ const config = await fs4.readJson(edtoolsConfigPath);
710
+ const hasApiKey = config.apiKey && config.apiKey.length > 0;
711
+ const provider = config.provider || "anthropic";
712
+ console.log(chalk3.green("\u2713") + " API key configured");
713
+ console.log(chalk3.gray(` Provider: ${chalk3.white(provider)}`));
714
+ console.log(chalk3.gray(` Key: ${chalk3.white(hasApiKey ? config.apiKey.substring(0, 10) + "..." : "Not set")}`));
715
+ console.log(chalk3.gray(` Location: ${edtoolsConfigPath}
716
+ `));
717
+ } catch (error) {
718
+ console.log(chalk3.red("\u2717") + " API key config is corrupted");
719
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools config set-api-key")} to fix it
720
+ `));
721
+ }
722
+ } else {
723
+ console.log(chalk3.red("\u2717") + " API key not configured");
724
+ console.log(chalk3.gray(` Run: ${chalk3.white("edtools config set-api-key")} or ${chalk3.white("edtools init")}
725
+ `));
726
+ }
727
+ const anthropicEnv = process.env.ANTHROPIC_API_KEY;
728
+ const openaiEnv = process.env.OPENAI_API_KEY;
729
+ if (anthropicEnv || openaiEnv) {
730
+ console.log(chalk3.cyan("Environment variables:"));
731
+ if (anthropicEnv) {
732
+ console.log(chalk3.gray(` ANTHROPIC_API_KEY: ${chalk3.white(anthropicEnv.substring(0, 10) + "...")}`));
733
+ }
734
+ if (openaiEnv) {
735
+ console.log(chalk3.gray(` OPENAI_API_KEY: ${chalk3.white(openaiEnv.substring(0, 10) + "...")}`));
736
+ }
737
+ console.log("");
738
+ }
739
+ console.log(chalk3.cyan("\u{1F4A1} Tip:"));
740
+ console.log(chalk3.gray(" API keys are searched in this order:"));
741
+ console.log(chalk3.gray(" 1. --api-key flag"));
742
+ console.log(chalk3.gray(" 2. .edtools/config.json"));
743
+ console.log(chalk3.gray(" 3. Environment variables\n"));
744
+ }
745
+
746
+ // src/cli/commands/generate.ts
469
747
  async function generateCommand(options) {
470
- console.log(chalk3.cyan.bold("\n\u{1F4DD} Generating content...\n"));
748
+ console.log(chalk4.cyan.bold("\n\u{1F4DD} Generating content...\n"));
471
749
  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'));
750
+ const configPath = path5.join(projectPath, "edtools.config.js");
751
+ if (!await fs5.pathExists(configPath)) {
752
+ console.log(chalk4.red("\u2717 No edtools.config.js found"));
753
+ console.log(chalk4.yellow(' Run "edtools init" first\n'));
476
754
  process.exit(1);
477
755
  }
478
756
  const configUrl = pathToFileURL(configPath).href;
479
757
  const config = await import(configUrl);
480
758
  const productInfo = config.default.product;
481
759
  if (!productInfo || !productInfo.name) {
482
- console.log(chalk3.red("\u2717 Invalid configuration in edtools.config.js"));
760
+ console.log(chalk4.red("\u2717 Invalid configuration in edtools.config.js"));
483
761
  process.exit(1);
484
762
  }
485
763
  const provider = productInfo.preferredProvider || "anthropic";
486
764
  let storedApiKey;
487
- const edtoolsConfigPath = path3.join(projectPath, ".edtools", "config.json");
488
- if (await fs3.pathExists(edtoolsConfigPath)) {
765
+ const edtoolsConfigPath = path5.join(projectPath, ".edtools", "config.json");
766
+ if (await fs5.pathExists(edtoolsConfigPath)) {
489
767
  try {
490
- const edtoolsConfig = await fs3.readJson(edtoolsConfigPath);
768
+ const edtoolsConfig = await fs5.readJson(edtoolsConfigPath);
491
769
  storedApiKey = edtoolsConfig.apiKey;
492
770
  } catch (error) {
493
771
  }
494
772
  }
495
773
  let apiKey;
774
+ let actualProvider = provider;
496
775
  if (provider === "anthropic") {
497
776
  apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY || storedApiKey;
498
777
  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);
778
+ console.log(chalk4.yellow("\n\u26A0\uFE0F No Anthropic API key configured\n"));
779
+ const { shouldConfigure } = await inquirer3.prompt([{
780
+ type: "confirm",
781
+ name: "shouldConfigure",
782
+ message: "Would you like to configure it now?",
783
+ default: true
784
+ }]);
785
+ if (shouldConfigure) {
786
+ const result = await promptForApiKey(projectPath);
787
+ if (result) {
788
+ apiKey = result.apiKey;
789
+ actualProvider = result.provider;
790
+ } else {
791
+ console.log(chalk4.red("\n\u2717 API key configuration failed\n"));
792
+ process.exit(1);
793
+ }
794
+ } else {
795
+ console.log(chalk4.cyan("\n\u{1F4A1} Quick fix options:\n"));
796
+ console.log(chalk4.gray(" 1. Run: ") + chalk4.white("edtools config set-api-key"));
797
+ console.log(chalk4.gray(" 2. Set env: ") + chalk4.white("export ANTHROPIC_API_KEY=sk-ant-..."));
798
+ console.log(chalk4.gray(" 3. Use flag: ") + chalk4.white("--api-key sk-ant-...\n"));
799
+ process.exit(1);
800
+ }
504
801
  }
505
802
  } else if (provider === "openai") {
506
803
  apiKey = options.apiKey || process.env.OPENAI_API_KEY || storedApiKey;
507
804
  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);
805
+ console.log(chalk4.yellow("\n\u26A0\uFE0F No OpenAI API key configured\n"));
806
+ const { shouldConfigure } = await inquirer3.prompt([{
807
+ type: "confirm",
808
+ name: "shouldConfigure",
809
+ message: "Would you like to configure it now?",
810
+ default: true
811
+ }]);
812
+ if (shouldConfigure) {
813
+ const result = await promptForApiKey(projectPath);
814
+ if (result) {
815
+ apiKey = result.apiKey;
816
+ actualProvider = result.provider;
817
+ } else {
818
+ console.log(chalk4.red("\n\u2717 API key configuration failed\n"));
819
+ process.exit(1);
820
+ }
821
+ } else {
822
+ console.log(chalk4.cyan("\n\u{1F4A1} Quick fix options:\n"));
823
+ console.log(chalk4.gray(" 1. Run: ") + chalk4.white("edtools config set-api-key"));
824
+ console.log(chalk4.gray(" 2. Set env: ") + chalk4.white("export OPENAI_API_KEY=sk-..."));
825
+ console.log(chalk4.gray(" 3. Use flag: ") + chalk4.white("--api-key sk-...\n"));
826
+ process.exit(1);
827
+ }
513
828
  }
514
829
  }
515
830
  let topics = options.topics;
516
831
  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'));
832
+ const opportunitiesPath = path5.join(projectPath, ".edtools", "opportunities.json");
833
+ if (!await fs5.pathExists(opportunitiesPath)) {
834
+ console.log(chalk4.red("\u2717 No CSV analysis found"));
835
+ console.log(chalk4.yellow(' Run "edtools analyze" first to analyze your GSC data\n'));
521
836
  process.exit(1);
522
837
  }
523
838
  try {
524
- const oppData = await fs3.readJson(opportunitiesPath);
839
+ const oppData = await fs5.readJson(opportunitiesPath);
525
840
  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"));
841
+ console.log(chalk4.cyan("\u{1F4CA} Using topics from CSV analysis:\n"));
527
842
  topics.forEach((topic, i) => {
528
- console.log(` ${chalk3.cyan((i + 1).toString() + ".")} ${chalk3.white(topic)}`);
843
+ console.log(` ${chalk4.cyan((i + 1).toString() + ".")} ${chalk4.white(topic)}`);
529
844
  });
530
845
  console.log("");
531
846
  } catch (error) {
532
- console.log(chalk3.red("\u2717 Failed to load CSV analysis"));
847
+ console.log(chalk4.red("\u2717 Failed to load CSV analysis"));
533
848
  process.exit(1);
534
849
  }
535
850
  }
536
851
  const count = parseInt(options.posts, 10);
537
852
  if (isNaN(count) || count < 1 || count > 10) {
538
- console.log(chalk3.red("\u2717 Invalid number of posts (must be 1-10)"));
853
+ console.log(chalk4.red("\u2717 Invalid number of posts (must be 1-10)"));
539
854
  process.exit(1);
540
855
  }
541
856
  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"));
857
+ console.log(chalk4.yellow(`\u26A0\uFE0F Generating ${count} posts at once may trigger spam detection`));
858
+ console.log(chalk4.yellow(" Recommended: 3-5 posts per week\n"));
544
859
  }
545
860
  let outputDirConfig;
546
861
  let outputDirSource;
@@ -554,48 +869,48 @@ async function generateCommand(options) {
554
869
  outputDirConfig = "./blog";
555
870
  outputDirSource = "default";
556
871
  }
557
- const outputDir = path3.resolve(projectPath, outputDirConfig);
558
- await fs3.ensureDir(outputDir);
872
+ const outputDir = path5.resolve(projectPath, outputDirConfig);
873
+ await fs5.ensureDir(outputDir);
559
874
  const hostingConfig = detectHostingConfig(projectPath);
560
875
  if (hostingConfig) {
561
876
  const validation = validateOutputDir(outputDirConfig, hostingConfig);
562
877
  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"));
878
+ console.log(chalk4.yellow.bold("\n\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk4.white(validation.platform));
879
+ console.log(chalk4.yellow(" - Public directory: ") + chalk4.white(validation.publicDir));
880
+ console.log(chalk4.yellow(" - Your outputDir: ") + chalk4.white(validation.currentOutputDir));
881
+ console.log(chalk4.yellow(" - Problem: Files outside ") + chalk4.white(validation.publicDir + "/") + chalk4.yellow(" won't be accessible"));
567
882
  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"));
883
+ console.log(chalk4.red.bold("\u274C ISSUE: Output directory misconfiguration"));
884
+ console.log(chalk4.red(" Generated files will return 404 in production"));
570
885
  console.log("");
571
- console.log(chalk3.cyan.bold("\u{1F4DD} Suggested fix:"));
572
- console.log(chalk3.cyan(" Update edtools.config.js:"));
886
+ console.log(chalk4.cyan.bold("\u{1F4DD} Suggested fix:"));
887
+ console.log(chalk4.cyan(" Update edtools.config.js:"));
573
888
  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(" }"));
889
+ console.log(chalk4.gray(" content: {"));
890
+ console.log(chalk4.green(` outputDir: '${validation.suggestion}'`) + chalk4.gray(" // \u2705 Correct path"));
891
+ console.log(chalk4.gray(" }"));
577
892
  console.log("");
578
- const answer = await inquirer2.prompt([{
893
+ const answer = await inquirer3.prompt([{
579
894
  type: "confirm",
580
895
  name: "continue",
581
896
  message: "Continue anyway?",
582
897
  default: false
583
898
  }]);
584
899
  if (!answer.continue) {
585
- console.log(chalk3.yellow("\n\u2717 Generation cancelled\n"));
900
+ console.log(chalk4.yellow("\n\u2717 Generation cancelled\n"));
586
901
  process.exit(0);
587
902
  }
588
903
  console.log("");
589
904
  }
590
905
  }
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 + ")")}
906
+ console.log(chalk4.cyan("Configuration:"));
907
+ console.log(` Product: ${chalk4.white(productInfo.name)}`);
908
+ console.log(` Category: ${chalk4.white(productInfo.category)}`);
909
+ console.log(` AI Provider: ${chalk4.white(actualProvider === "anthropic" ? "Claude (Anthropic)" : "ChatGPT (OpenAI)")}`);
910
+ console.log(` Posts to generate: ${chalk4.white(count)}`);
911
+ console.log(` Output directory: ${chalk4.white(outputDir)} ${chalk4.gray("(" + outputDirSource + ")")}
597
912
  `);
598
- const generator = new ContentGenerator(apiKey, provider);
913
+ const generator = new ContentGenerator(apiKey, actualProvider);
599
914
  const generateConfig = {
600
915
  productInfo,
601
916
  topics,
@@ -604,20 +919,22 @@ async function generateCommand(options) {
604
919
  projectPath,
605
920
  avoidDuplicates: true,
606
921
  similarityThreshold: 0.85,
607
- provider,
922
+ provider: actualProvider,
608
923
  apiKey,
609
- dryRun: options.dryRun
924
+ dryRun: options.dryRun,
925
+ blogConfig: config.default.blog
926
+ // Pass blog config from edtools.config.js
610
927
  };
611
- const providerName = provider === "anthropic" ? "Claude" : "ChatGPT";
928
+ const providerName = actualProvider === "anthropic" ? "Claude" : "ChatGPT";
612
929
  const spinner = ora2(`Generating content with ${providerName}...`).start();
613
930
  try {
614
931
  const result = await generator.generate(generateConfig);
615
932
  if (result.success && result.posts.length > 0) {
616
933
  const dryRunMode = result.dryRun || false;
617
- spinner.succeed(chalk3.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
934
+ spinner.succeed(chalk4.bold(dryRunMode ? "Preview generated successfully!" : "Content generated successfully!"));
618
935
  console.log("");
619
936
  if (dryRunMode) {
620
- console.log(chalk3.yellow.bold("\u{1F50D} DRY RUN MODE - No files were written\n"));
937
+ console.log(chalk4.yellow.bold("\u{1F50D} DRY RUN MODE - No files were written\n"));
621
938
  }
622
939
  if (options.report === "json" || options.report === "pretty") {
623
940
  const report = {
@@ -656,11 +973,11 @@ async function generateCommand(options) {
656
973
  } else {
657
974
  const table = new Table({
658
975
  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")
976
+ chalk4.cyan.bold("#"),
977
+ chalk4.cyan.bold("Title"),
978
+ chalk4.cyan.bold("Words"),
979
+ chalk4.cyan.bold("SEO"),
980
+ dryRunMode ? chalk4.cyan.bold("Would Create") : chalk4.cyan.bold("Path")
664
981
  ],
665
982
  colWidths: [5, 35, 10, 10, 45],
666
983
  wordWrap: true,
@@ -672,29 +989,29 @@ async function generateCommand(options) {
672
989
  result.posts.forEach((post, i) => {
673
990
  const scoreColor = getScoreColorFn(post.seoScore);
674
991
  table.push([
675
- chalk3.gray((i + 1).toString()),
676
- chalk3.white(truncateTitle(post.title, 33)),
677
- chalk3.white(post.wordCount.toString()),
992
+ chalk4.gray((i + 1).toString()),
993
+ chalk4.white(truncateTitle(post.title, 33)),
994
+ chalk4.white(post.wordCount.toString()),
678
995
  scoreColor(`${post.seoScore}/100`),
679
- chalk3.gray(post.path)
996
+ chalk4.gray(post.path)
680
997
  ]);
681
998
  });
682
999
  console.log(table.toString());
683
1000
  console.log("");
684
1001
  if (result.stats) {
685
- console.log(chalk3.cyan.bold("Statistics:\n"));
686
- console.log(` Total words: ${chalk3.white(result.stats.totalWords)}`);
1002
+ console.log(chalk4.cyan.bold("Statistics:\n"));
1003
+ console.log(` Total words: ${chalk4.white(result.stats.totalWords)}`);
687
1004
  console.log(` Avg SEO score: ${getScoreColorFn(result.stats.avgSeoScore)(result.stats.avgSeoScore + "/100")}`);
688
- console.log(` Avg read time: ${chalk3.white(result.stats.avgReadTime)}`);
1005
+ console.log(` Avg read time: ${chalk4.white(result.stats.avgReadTime)}`);
689
1006
  console.log("");
690
1007
  }
691
1008
  if (dryRunMode) {
692
- console.log(chalk3.yellow.bold("Would create:\n"));
1009
+ console.log(chalk4.yellow.bold("Would create:\n"));
693
1010
  if (result.manifestPath) {
694
- console.log(` ${chalk3.yellow("\u2022")} ${chalk3.white(result.manifestPath)} (manifest with ${result.posts.length} posts)`);
1011
+ console.log(` ${chalk4.yellow("\u2022")} ${chalk4.white(result.manifestPath)} (manifest with ${result.posts.length} posts)`);
695
1012
  }
696
1013
  if (result.sitemapPath) {
697
- console.log(` ${chalk3.yellow("\u2022")} ${chalk3.white(result.sitemapPath)} (sitemap with ${result.posts.length} URLs)`);
1014
+ console.log(` ${chalk4.yellow("\u2022")} ${chalk4.white(result.sitemapPath)} (sitemap with ${result.posts.length} URLs)`);
698
1015
  }
699
1016
  console.log("");
700
1017
  }
@@ -706,21 +1023,21 @@ async function generateCommand(options) {
706
1023
  console.log(warningBox(warningText));
707
1024
  }
708
1025
  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
1026
+ console.log(chalk4.cyan.bold("Next step:"));
1027
+ console.log(` ${chalk4.cyan("\u2022")} Run without ${chalk4.white("--dry-run")} to generate files
711
1028
  `);
712
1029
  } 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`);
1030
+ console.log(chalk4.cyan.bold("Next steps:"));
1031
+ console.log(` ${chalk4.cyan("1.")} Review generated content in ${chalk4.white(outputDir)}`);
1032
+ console.log(` ${chalk4.cyan("2.")} Edit posts to add personal experience/expertise`);
1033
+ console.log(` ${chalk4.cyan("3.")} Deploy to your website`);
717
1034
  console.log("");
718
- console.log(chalk3.yellow(`\u{1F4A1} Tip: Wait 3-7 days before generating more posts to avoid spam detection
1035
+ console.log(chalk4.yellow(`\u{1F4A1} Tip: Wait 3-7 days before generating more posts to avoid spam detection
719
1036
  `));
720
1037
  }
721
1038
  }
722
1039
  } else {
723
- spinner.fail(chalk3.bold("Failed to generate content"));
1040
+ spinner.fail(chalk4.bold("Failed to generate content"));
724
1041
  if (result.errors && result.errors.length > 0) {
725
1042
  const errorText = result.errors.join("\n");
726
1043
  console.log(errorBox(errorText));
@@ -728,16 +1045,16 @@ async function generateCommand(options) {
728
1045
  process.exit(1);
729
1046
  }
730
1047
  } catch (error) {
731
- spinner.fail(chalk3.bold("Error during generation"));
1048
+ spinner.fail(chalk4.bold("Error during generation"));
732
1049
  console.log(errorBox(error.message));
733
1050
  process.exit(1);
734
1051
  }
735
1052
  }
736
1053
  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;
1054
+ if (score >= 90) return chalk4.green;
1055
+ if (score >= 75) return chalk4.yellow;
1056
+ if (score >= 60) return chalk4.hex("#FFA500");
1057
+ return chalk4.red;
741
1058
  }
742
1059
  function truncateTitle(title, maxLen) {
743
1060
  if (title.length <= maxLen) return title;
@@ -745,17 +1062,17 @@ function truncateTitle(title, maxLen) {
745
1062
  }
746
1063
 
747
1064
  // src/cli/commands/analyze.ts
748
- import fs5 from "fs-extra";
749
- import path4 from "path";
750
- import chalk4 from "chalk";
1065
+ import fs7 from "fs-extra";
1066
+ import path6 from "path";
1067
+ import chalk5 from "chalk";
751
1068
  import ora3 from "ora";
752
1069
  import Table2 from "cli-table3";
753
1070
 
754
1071
  // src/integrations/gsc-csv-analyzer.ts
755
- import fs4 from "fs-extra";
1072
+ import fs6 from "fs-extra";
756
1073
  import { parse } from "csv-parse/sync";
757
1074
  async function parseGSCCSV(filePath) {
758
- const content = await fs4.readFile(filePath, "utf-8");
1075
+ const content = await fs6.readFile(filePath, "utf-8");
759
1076
  const records = parse(content, {
760
1077
  columns: true,
761
1078
  skip_empty_lines: true,
@@ -851,34 +1168,34 @@ async function saveOpportunities(opportunities, outputPath) {
851
1168
  count: opportunities.length,
852
1169
  opportunities
853
1170
  };
854
- await fs4.writeJson(outputPath, data, { spaces: 2 });
1171
+ await fs6.writeJson(outputPath, data, { spaces: 2 });
855
1172
  }
856
1173
 
857
1174
  // src/cli/commands/analyze.ts
858
1175
  async function analyzeCommand(options) {
859
- console.log(chalk4.cyan.bold("\n\u{1F4CA} Analyzing Google Search Console Data...\n"));
1176
+ console.log(chalk5.cyan.bold("\n\u{1F4CA} Analyzing Google Search Console Data...\n"));
860
1177
  const projectPath = process.cwd();
861
1178
  let csvPath;
862
1179
  if (options.file) {
863
- csvPath = path4.resolve(options.file);
1180
+ csvPath = path6.resolve(options.file);
864
1181
  } else {
865
- const files = await fs5.readdir(projectPath);
1182
+ const files = await fs7.readdir(projectPath);
866
1183
  const csvFiles = files.filter((f) => f.endsWith(".csv") && f.toLowerCase().includes("search"));
867
1184
  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"));
1185
+ console.log(chalk5.red("\u2717 No CSV file found"));
1186
+ console.log(chalk5.yellow(" Please provide a CSV file using --file option"));
1187
+ console.log(chalk5.yellow(" Example: edtools analyze --file search-console-data.csv\n"));
871
1188
  process.exit(1);
872
1189
  }
873
1190
  if (csvFiles.length > 1) {
874
- console.log(chalk4.yellow("\u26A0 Multiple CSV files found:"));
1191
+ console.log(chalk5.yellow("\u26A0 Multiple CSV files found:"));
875
1192
  csvFiles.forEach((f) => console.log(` - ${f}`));
876
- console.log(chalk4.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
1193
+ console.log(chalk5.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
877
1194
  }
878
- csvPath = path4.join(projectPath, csvFiles[0]);
1195
+ csvPath = path6.join(projectPath, csvFiles[0]);
879
1196
  }
880
- if (!await fs5.pathExists(csvPath)) {
881
- console.log(chalk4.red(`\u2717 File not found: ${csvPath}
1197
+ if (!await fs7.pathExists(csvPath)) {
1198
+ console.log(chalk5.red(`\u2717 File not found: ${csvPath}
882
1199
  `));
883
1200
  process.exit(1);
884
1201
  }
@@ -886,10 +1203,10 @@ async function analyzeCommand(options) {
886
1203
  let data;
887
1204
  try {
888
1205
  data = await parseGSCCSV(csvPath);
889
- spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(path4.basename(csvPath))}`);
1206
+ spinner.succeed(`Parsed ${chalk5.white(data.length)} keywords from ${chalk5.white(path6.basename(csvPath))}`);
890
1207
  } catch (error) {
891
1208
  spinner.fail("Failed to parse CSV");
892
- console.log(chalk4.red(`
1209
+ console.log(chalk5.red(`
893
1210
  Error: ${error.message}
894
1211
  `));
895
1212
  process.exit(1);
@@ -904,7 +1221,7 @@ Error: ${error.message}
904
1221
  analyzeSpinner.succeed("Analysis complete!");
905
1222
  console.log("");
906
1223
  const metricsTable = new Table2({
907
- head: [chalk4.cyan.bold("Metric"), chalk4.cyan.bold("Value")],
1224
+ head: [chalk5.cyan.bold("Metric"), chalk5.cyan.bold("Value")],
908
1225
  colWidths: [30, 20],
909
1226
  style: {
910
1227
  head: [],
@@ -912,28 +1229,28 @@ Error: ${error.message}
912
1229
  }
913
1230
  });
914
1231
  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))]
1232
+ ["Total Keywords", chalk5.white(analysis.totalQueries.toLocaleString())],
1233
+ ["Total Impressions", chalk5.white(analysis.totalImpressions.toLocaleString())],
1234
+ ["Total Clicks", chalk5.white(analysis.totalClicks.toLocaleString())],
1235
+ ["Average CTR", chalk5.white((analysis.avgCTR * 100).toFixed(2) + "%")],
1236
+ ["Average Position", chalk5.white(analysis.avgPosition.toFixed(1))]
920
1237
  );
921
- console.log(chalk4.bold.cyan("\u{1F4C8} Overall Metrics\n"));
1238
+ console.log(chalk5.bold.cyan("\u{1F4C8} Overall Metrics\n"));
922
1239
  console.log(metricsTable.toString());
923
1240
  console.log("");
924
1241
  if (analysis.opportunities.length === 0) {
925
1242
  console.log(warningBox("No opportunities found with current criteria.\nTry lowering --min-impressions or increasing --min-position."));
926
1243
  } else {
927
- console.log(chalk4.bold.cyan(`\u{1F3AF} Top ${analysis.opportunities.length} Content Opportunities
1244
+ console.log(chalk5.bold.cyan(`\u{1F3AF} Top ${analysis.opportunities.length} Content Opportunities
928
1245
  `));
929
1246
  const oppTable = new Table2({
930
1247
  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")
1248
+ chalk5.cyan.bold("#"),
1249
+ chalk5.cyan.bold("Keyword"),
1250
+ chalk5.cyan.bold("Impressions"),
1251
+ chalk5.cyan.bold("Position"),
1252
+ chalk5.cyan.bold("Potential"),
1253
+ chalk5.cyan.bold("Priority")
937
1254
  ],
938
1255
  colWidths: [5, 40, 14, 12, 12, 12],
939
1256
  wordWrap: true,
@@ -943,34 +1260,34 @@ Error: ${error.message}
943
1260
  }
944
1261
  });
945
1262
  analysis.opportunities.forEach((opp, i) => {
946
- const priorityColor = opp.priority === "high" ? chalk4.green : opp.priority === "medium" ? chalk4.yellow : chalk4.gray;
1263
+ const priorityColor = opp.priority === "high" ? chalk5.green : opp.priority === "medium" ? chalk5.yellow : chalk5.gray;
947
1264
  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}`),
1265
+ chalk5.gray((i + 1).toString()),
1266
+ chalk5.white(opp.keyword),
1267
+ chalk5.cyan(opp.impressions.toLocaleString()),
1268
+ chalk5.yellow(opp.position.toFixed(1)),
1269
+ chalk5.green(`+${opp.potentialClicks}`),
953
1270
  priorityColor(opp.priority.toUpperCase())
954
1271
  ]);
955
1272
  });
956
1273
  console.log(oppTable.toString());
957
1274
  console.log("");
958
- console.log(chalk4.bold.cyan("\u{1F4A1} Suggested Blog Post Titles\n"));
1275
+ console.log(chalk5.bold.cyan("\u{1F4A1} Suggested Blog Post Titles\n"));
959
1276
  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)}
1277
+ console.log(` ${chalk5.cyan((i + 1).toString() + ".")} ${chalk5.white(opp.suggestedTitle)}`);
1278
+ console.log(` ${chalk5.gray(opp.reason)}
962
1279
  `);
963
1280
  });
964
1281
  }
965
1282
  if (analysis.topKeywords.length > 0) {
966
- console.log(chalk4.bold.cyan("\u2B50 Top Performing Keywords (by clicks)\n"));
1283
+ console.log(chalk5.bold.cyan("\u2B50 Top Performing Keywords (by clicks)\n"));
967
1284
  const topTable = new Table2({
968
1285
  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")
1286
+ chalk5.cyan.bold("#"),
1287
+ chalk5.cyan.bold("Keyword"),
1288
+ chalk5.cyan.bold("Clicks"),
1289
+ chalk5.cyan.bold("Impressions"),
1290
+ chalk5.cyan.bold("CTR")
974
1291
  ],
975
1292
  colWidths: [5, 40, 12, 14, 12],
976
1293
  wordWrap: true,
@@ -981,39 +1298,39 @@ Error: ${error.message}
981
1298
  });
982
1299
  analysis.topKeywords.slice(0, 5).forEach((kw, i) => {
983
1300
  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) + "%")
1301
+ chalk5.gray((i + 1).toString()),
1302
+ chalk5.white(kw.query),
1303
+ chalk5.green(kw.clicks.toString()),
1304
+ chalk5.cyan(kw.impressions.toLocaleString()),
1305
+ chalk5.yellow((kw.ctr * 100).toFixed(2) + "%")
989
1306
  ]);
990
1307
  });
991
1308
  console.log(topTable.toString());
992
1309
  console.log("");
993
1310
  }
994
- const edtoolsDir = path4.join(projectPath, ".edtools");
995
- await fs5.ensureDir(edtoolsDir);
996
- const opportunitiesPath = path4.join(edtoolsDir, "opportunities.json");
1311
+ const edtoolsDir = path6.join(projectPath, ".edtools");
1312
+ await fs7.ensureDir(edtoolsDir);
1313
+ const opportunitiesPath = path6.join(edtoolsDir, "opportunities.json");
997
1314
  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"')}
1315
+ console.log(successBox(`Analysis saved to ${chalk5.white(".edtools/opportunities.json")}`));
1316
+ console.log(chalk5.cyan.bold("Next steps:"));
1317
+ console.log(` ${chalk5.cyan("1.")} Generate content: ${chalk5.white("edtools generate --from-csv")}`);
1318
+ console.log(` ${chalk5.cyan("2.")} Or specify topics: ${chalk5.white('edtools generate -t "topic 1" "topic 2"')}
1002
1319
  `);
1003
- console.log(chalk4.gray("\u{1F4A1} Use --from-csv to automatically generate posts for top opportunities\n"));
1320
+ console.log(chalk5.gray("\u{1F4A1} Use --from-csv to automatically generate posts for top opportunities\n"));
1004
1321
  }
1005
1322
 
1006
1323
  // src/cli/commands/validate.ts
1007
- import fs7 from "fs-extra";
1008
- import path5 from "path";
1009
- import chalk5 from "chalk";
1324
+ import fs9 from "fs-extra";
1325
+ import path7 from "path";
1326
+ import chalk6 from "chalk";
1010
1327
  import Table3 from "cli-table3";
1011
1328
 
1012
1329
  // src/utils/seo-validator.ts
1013
1330
  import * as cheerio from "cheerio";
1014
- import fs6 from "fs-extra";
1331
+ import fs8 from "fs-extra";
1015
1332
  async function validatePost(htmlPath) {
1016
- const html = await fs6.readFile(htmlPath, "utf-8");
1333
+ const html = await fs8.readFile(htmlPath, "utf-8");
1017
1334
  const $ = cheerio.load(html);
1018
1335
  const issues = [];
1019
1336
  const passed = [];
@@ -1236,19 +1553,19 @@ function calculateValidationStats(results) {
1236
1553
 
1237
1554
  // src/cli/commands/validate.ts
1238
1555
  async function validateCommand(options) {
1239
- console.log(chalk5.cyan.bold("\n\u{1F4CA} Validating SEO quality...\n"));
1556
+ console.log(chalk6.cyan.bold("\n\u{1F4CA} Validating SEO quality...\n"));
1240
1557
  const projectPath = process.cwd();
1241
1558
  let htmlFiles = [];
1242
1559
  if (options.post) {
1243
- const postPath = path5.resolve(projectPath, options.post);
1244
- if (!await fs7.pathExists(postPath)) {
1560
+ const postPath = path7.resolve(projectPath, options.post);
1561
+ if (!await fs9.pathExists(postPath)) {
1245
1562
  console.log(errorBox(`Post not found: ${postPath}`));
1246
1563
  process.exit(1);
1247
1564
  }
1248
1565
  htmlFiles = [postPath];
1249
1566
  } else if (options.posts) {
1250
- const postsDir = path5.resolve(projectPath, options.posts);
1251
- if (!await fs7.pathExists(postsDir)) {
1567
+ const postsDir = path7.resolve(projectPath, options.posts);
1568
+ if (!await fs9.pathExists(postsDir)) {
1252
1569
  console.log(errorBox(`Directory not found: ${postsDir}`));
1253
1570
  process.exit(1);
1254
1571
  }
@@ -1258,8 +1575,8 @@ async function validateCommand(options) {
1258
1575
  process.exit(1);
1259
1576
  }
1260
1577
  } else {
1261
- const defaultBlogDir = path5.join(projectPath, "blog");
1262
- if (await fs7.pathExists(defaultBlogDir)) {
1578
+ const defaultBlogDir = path7.join(projectPath, "blog");
1579
+ if (await fs9.pathExists(defaultBlogDir)) {
1263
1580
  htmlFiles = await findHtmlFiles(defaultBlogDir);
1264
1581
  if (htmlFiles.length === 0) {
1265
1582
  console.log(errorBox("No HTML files found in blog/ directory"));
@@ -1267,11 +1584,11 @@ async function validateCommand(options) {
1267
1584
  }
1268
1585
  } else {
1269
1586
  console.log(errorBox("No posts directory specified"));
1270
- console.log(chalk5.yellow("Usage: edtools validate --posts <dir> or --post <file>\n"));
1587
+ console.log(chalk6.yellow("Usage: edtools validate --posts <dir> or --post <file>\n"));
1271
1588
  process.exit(1);
1272
1589
  }
1273
1590
  }
1274
- console.log(chalk5.cyan(`Found ${htmlFiles.length} post${htmlFiles.length > 1 ? "s" : ""} to validate
1591
+ console.log(chalk6.cyan(`Found ${htmlFiles.length} post${htmlFiles.length > 1 ? "s" : ""} to validate
1275
1592
  `));
1276
1593
  const results = [];
1277
1594
  for (const htmlFile of htmlFiles) {
@@ -1279,7 +1596,7 @@ async function validateCommand(options) {
1279
1596
  const result = await validatePost(htmlFile);
1280
1597
  results.push(result);
1281
1598
  } catch (error) {
1282
- console.log(chalk5.yellow(`\u26A0\uFE0F Failed to validate ${htmlFile}: ${error.message}`));
1599
+ console.log(chalk6.yellow(`\u26A0\uFE0F Failed to validate ${htmlFile}: ${error.message}`));
1283
1600
  }
1284
1601
  }
1285
1602
  if (results.length === 0) {
@@ -1304,17 +1621,17 @@ async function validateCommand(options) {
1304
1621
  })),
1305
1622
  stats: stats2
1306
1623
  };
1307
- const outputPath = path5.resolve(projectPath, options.output);
1308
- await fs7.writeJson(outputPath, report, { spaces: 2 });
1624
+ const outputPath = path7.resolve(projectPath, options.output);
1625
+ await fs9.writeJson(outputPath, report, { spaces: 2 });
1309
1626
  console.log(successBox(`Validation report saved to ${outputPath}`));
1310
1627
  }
1311
1628
  if (filteredResults.length > 0) {
1312
1629
  const table = new Table3({
1313
1630
  head: [
1314
- chalk5.cyan.bold("#"),
1315
- chalk5.cyan.bold("Title"),
1316
- chalk5.cyan.bold("Score"),
1317
- chalk5.cyan.bold("Issues")
1631
+ chalk6.cyan.bold("#"),
1632
+ chalk6.cyan.bold("Title"),
1633
+ chalk6.cyan.bold("Score"),
1634
+ chalk6.cyan.bold("Issues")
1318
1635
  ],
1319
1636
  colWidths: [5, 40, 10, 50],
1320
1637
  wordWrap: true,
@@ -1327,12 +1644,12 @@ async function validateCommand(options) {
1327
1644
  const scoreColor = getScoreColor(result.seoScore);
1328
1645
  const issuesText = result.issues.length > 0 ? result.issues.map((issue) => {
1329
1646
  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;
1647
+ const color = issue.severity === "error" ? chalk6.red : issue.severity === "warning" ? chalk6.yellow : chalk6.blue;
1331
1648
  return color(`${icon} ${issue.message}`);
1332
- }).join("\n") : chalk5.green("\u2713 No issues");
1649
+ }).join("\n") : chalk6.green("\u2713 No issues");
1333
1650
  table.push([
1334
- chalk5.gray((i + 1).toString()),
1335
- chalk5.white(truncate(result.title, 38)),
1651
+ chalk6.gray((i + 1).toString()),
1652
+ chalk6.white(truncate(result.title, 38)),
1336
1653
  scoreColor(`${result.seoScore}/100`),
1337
1654
  issuesText
1338
1655
  ]);
@@ -1343,39 +1660,39 @@ async function validateCommand(options) {
1343
1660
  console.log(successBox(`All posts have SEO score >= ${threshold}`));
1344
1661
  }
1345
1662
  const stats = calculateValidationStats(results);
1346
- console.log(chalk5.cyan.bold("Overall Statistics:\n"));
1347
- console.log(` Total posts validated: ${chalk5.white(stats.totalPosts)}`);
1663
+ console.log(chalk6.cyan.bold("Overall Statistics:\n"));
1664
+ console.log(` Total posts validated: ${chalk6.white(stats.totalPosts)}`);
1348
1665
  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)}`);
1666
+ console.log(` Posts with issues: ${chalk6.white(stats.postsWithIssues)} (${Math.round(stats.postsWithIssues / stats.totalPosts * 100)}%)`);
1667
+ console.log(` Total issues found: ${chalk6.white(stats.totalIssues)}`);
1351
1668
  console.log("");
1352
1669
  if (stats.totalIssues > 0) {
1353
- console.log(chalk5.cyan.bold("Issues by Severity:\n"));
1670
+ console.log(chalk6.cyan.bold("Issues by Severity:\n"));
1354
1671
  if (stats.issuesBySeverity.error > 0) {
1355
- console.log(` ${chalk5.red("\u2717 Errors:")} ${chalk5.white(stats.issuesBySeverity.error)}`);
1672
+ console.log(` ${chalk6.red("\u2717 Errors:")} ${chalk6.white(stats.issuesBySeverity.error)}`);
1356
1673
  }
1357
1674
  if (stats.issuesBySeverity.warning > 0) {
1358
- console.log(` ${chalk5.yellow("\u26A0 Warnings:")} ${chalk5.white(stats.issuesBySeverity.warning)}`);
1675
+ console.log(` ${chalk6.yellow("\u26A0 Warnings:")} ${chalk6.white(stats.issuesBySeverity.warning)}`);
1359
1676
  }
1360
1677
  if (stats.issuesBySeverity.info > 0) {
1361
- console.log(` ${chalk5.blue("\u2139 Info:")} ${chalk5.white(stats.issuesBySeverity.info)}`);
1678
+ console.log(` ${chalk6.blue("\u2139 Info:")} ${chalk6.white(stats.issuesBySeverity.info)}`);
1362
1679
  }
1363
1680
  console.log("");
1364
1681
  }
1365
1682
  if (Object.keys(stats.issuesByCategory).length > 0) {
1366
- console.log(chalk5.cyan.bold("Top Issue Categories:\n"));
1683
+ console.log(chalk6.cyan.bold("Top Issue Categories:\n"));
1367
1684
  const sortedCategories = Object.entries(stats.issuesByCategory).sort((a, b) => b[1] - a[1]).slice(0, 5);
1368
1685
  sortedCategories.forEach(([category, count]) => {
1369
- console.log(` ${chalk5.white(capitalize(category))}: ${chalk5.yellow(count)}`);
1686
+ console.log(` ${chalk6.white(capitalize(category))}: ${chalk6.yellow(count)}`);
1370
1687
  });
1371
1688
  console.log("");
1372
1689
  }
1373
1690
  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"));
1691
+ console.log(chalk6.yellow.bold("\u{1F4A1} Recommendations:\n"));
1692
+ console.log(chalk6.yellow(" \u2022 Focus on fixing critical errors first"));
1693
+ console.log(chalk6.yellow(" \u2022 Optimize meta descriptions to 150-160 characters"));
1694
+ console.log(chalk6.yellow(" \u2022 Ensure all posts have proper heading structure (H1, H2s)"));
1695
+ console.log(chalk6.yellow(" \u2022 Add alt text to all images"));
1379
1696
  console.log("");
1380
1697
  } else {
1381
1698
  console.log(successBox(`Great job! Average SEO score is ${stats.avgSeoScore}/100`));
@@ -1383,9 +1700,9 @@ async function validateCommand(options) {
1383
1700
  }
1384
1701
  async function findHtmlFiles(dir) {
1385
1702
  const files = [];
1386
- const entries = await fs7.readdir(dir, { withFileTypes: true });
1703
+ const entries = await fs9.readdir(dir, { withFileTypes: true });
1387
1704
  for (const entry of entries) {
1388
- const fullPath = path5.join(dir, entry.name);
1705
+ const fullPath = path7.join(dir, entry.name);
1389
1706
  if (entry.isDirectory()) {
1390
1707
  const subFiles = await findHtmlFiles(fullPath);
1391
1708
  files.push(...subFiles);
@@ -1396,10 +1713,10 @@ async function findHtmlFiles(dir) {
1396
1713
  return files;
1397
1714
  }
1398
1715
  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;
1716
+ if (score >= 90) return chalk6.green;
1717
+ if (score >= 75) return chalk6.yellow;
1718
+ if (score >= 60) return chalk6.hex("#FFA500");
1719
+ return chalk6.red;
1403
1720
  }
1404
1721
  function truncate(str, maxLen) {
1405
1722
  if (str.length <= maxLen) return str;
@@ -1410,22 +1727,22 @@ function capitalize(str) {
1410
1727
  }
1411
1728
 
1412
1729
  // src/cli/commands/doctor.ts
1413
- import fs8 from "fs-extra";
1414
- import path6 from "path";
1730
+ import fs10 from "fs-extra";
1731
+ import path8 from "path";
1415
1732
  import { pathToFileURL as pathToFileURL2 } from "url";
1416
- import chalk6 from "chalk";
1733
+ import chalk7 from "chalk";
1417
1734
  async function doctorCommand(options) {
1418
- console.log(chalk6.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
1735
+ console.log(chalk7.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
1419
1736
  const projectPath = process.cwd();
1420
- const configPath = path6.join(projectPath, "edtools.config.js");
1737
+ const configPath = path8.join(projectPath, "edtools.config.js");
1421
1738
  const issues = [];
1422
1739
  const warnings = [];
1423
1740
  const suggestions = [];
1424
- if (await fs8.pathExists(configPath)) {
1425
- console.log(chalk6.green("\u2713 Configuration file found: ") + chalk6.white("edtools.config.js"));
1741
+ if (await fs10.pathExists(configPath)) {
1742
+ console.log(chalk7.green("\u2713 Configuration file found: ") + chalk7.white("edtools.config.js"));
1426
1743
  } else {
1427
- console.log(chalk6.red("\u2717 Configuration file not found"));
1428
- console.log(chalk6.yellow(' Run "edtools init" to create configuration\n'));
1744
+ console.log(chalk7.red("\u2717 Configuration file not found"));
1745
+ console.log(chalk7.yellow(' Run "edtools init" to create configuration\n'));
1429
1746
  process.exit(1);
1430
1747
  }
1431
1748
  let productInfo;
@@ -1440,135 +1757,153 @@ async function doctorCommand(options) {
1440
1757
  }
1441
1758
  } catch (error) {
1442
1759
  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}
1760
+ console.log(chalk7.red("\u2717 Failed to load configuration file"));
1761
+ console.log(chalk7.red(` Error: ${error.message}
1445
1762
  `));
1446
1763
  process.exit(1);
1447
1764
  }
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));
1765
+ const outputDirPath = path8.resolve(projectPath, outputDir);
1766
+ if (await fs10.pathExists(outputDirPath)) {
1767
+ console.log(chalk7.green("\u2713 Output directory exists: ") + chalk7.white(outputDir));
1451
1768
  } else {
1452
1769
  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"'));
1770
+ console.log(chalk7.yellow("\u26A0\uFE0F Output directory does not exist: ") + chalk7.white(outputDir));
1771
+ console.log(chalk7.yellow(' It will be created when you run "edtools generate"'));
1455
1772
  }
1456
- const edtoolsConfigPath = path6.join(projectPath, ".edtools", "config.json");
1773
+ const edtoolsConfigPath = path8.join(projectPath, ".edtools", "config.json");
1457
1774
  let hasApiKey = false;
1458
- if (await fs8.pathExists(edtoolsConfigPath)) {
1775
+ if (await fs10.pathExists(edtoolsConfigPath)) {
1459
1776
  try {
1460
- const edtoolsConfig = await fs8.readJson(edtoolsConfigPath);
1777
+ const edtoolsConfig = await fs10.readJson(edtoolsConfigPath);
1461
1778
  hasApiKey = !!edtoolsConfig.apiKey;
1462
1779
  } catch (error) {
1463
1780
  }
1464
1781
  }
1465
1782
  const hasEnvApiKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
1466
1783
  if (hasApiKey || hasEnvApiKey) {
1467
- console.log(chalk6.green("\u2713 API key configured"));
1784
+ console.log(chalk7.green("\u2713 API key configured"));
1468
1785
  } else {
1469
1786
  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'));
1787
+ console.log(chalk7.yellow("\u26A0\uFE0F No API key configured"));
1788
+ console.log(chalk7.yellow(" Set ANTHROPIC_API_KEY or OPENAI_API_KEY environment variable"));
1789
+ console.log(chalk7.yellow(' Or run "edtools init" to store API key'));
1473
1790
  }
1474
1791
  console.log("");
1475
1792
  const hostingConfig = detectHostingConfig(projectPath);
1476
1793
  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));
1794
+ console.log(chalk7.cyan("\u26A0\uFE0F HOSTING PLATFORM DETECTED: ") + chalk7.white(hostingConfig.platform));
1795
+ console.log(chalk7.cyan(" - Public directory: ") + chalk7.white(hostingConfig.publicDir));
1796
+ console.log(chalk7.cyan(" - Your outputDir: ") + chalk7.white(outputDir));
1480
1797
  const validation = validateOutputDir(outputDir, hostingConfig);
1481
1798
  if (!validation.valid) {
1482
1799
  issues.push("Output directory misconfiguration");
1483
- console.log(chalk6.red(" - Problem: Files outside ") + chalk6.white(hostingConfig.publicDir + "/") + chalk6.red(" won't be accessible"));
1800
+ console.log(chalk7.red(" - Problem: Files outside ") + chalk7.white(hostingConfig.publicDir + "/") + chalk7.red(" won't be accessible"));
1484
1801
  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"));
1802
+ console.log(chalk7.red.bold("\u274C ISSUE: Output directory misconfiguration"));
1803
+ console.log(chalk7.red(" Generated files will return 404 in production"));
1487
1804
  console.log("");
1488
- console.log(chalk6.cyan.bold("\u{1F4DD} Suggested fix:"));
1489
- console.log(chalk6.cyan(" Update edtools.config.js:"));
1805
+ console.log(chalk7.cyan.bold("\u{1F4DD} Suggested fix:"));
1806
+ console.log(chalk7.cyan(" Update edtools.config.js:"));
1490
1807
  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(" }"));
1808
+ console.log(chalk7.gray(" content: {"));
1809
+ console.log(chalk7.green(` outputDir: '${validation.suggestion}'`) + chalk7.gray(" // \u2705 Correct path"));
1810
+ console.log(chalk7.gray(" }"));
1494
1811
  console.log("");
1495
1812
  suggestions.push(`Update outputDir to '${validation.suggestion}'`);
1496
1813
  if (options.fix) {
1497
- console.log(chalk6.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1814
+ console.log(chalk7.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1498
1815
  try {
1499
- const configContent = await fs8.readFile(configPath, "utf-8");
1816
+ const configContent = await fs10.readFile(configPath, "utf-8");
1500
1817
  const updatedConfig = configContent.replace(
1501
1818
  /outputDir:\s*['"]([^'"]+)['"]/,
1502
1819
  `outputDir: '${validation.suggestion}'`
1503
1820
  );
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}'`));
1821
+ await fs10.writeFile(configPath, updatedConfig, "utf-8");
1822
+ console.log(chalk7.green("\u2713 Updated edtools.config.js"));
1823
+ console.log(chalk7.green(` outputDir: '${validation.suggestion}'`));
1507
1824
  console.log("");
1508
- console.log(chalk6.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
1825
+ console.log(chalk7.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
1509
1826
  } 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"));
1827
+ console.log(chalk7.red("\u2717 Failed to auto-fix configuration"));
1828
+ console.log(chalk7.red(` Error: ${error.message}`));
1829
+ console.log(chalk7.yellow(" Please update edtools.config.js manually\n"));
1513
1830
  }
1514
1831
  } else {
1515
- console.log(chalk6.cyan.bold("Run: ") + chalk6.white("edtools doctor --fix"));
1516
- console.log(chalk6.cyan("To automatically apply suggested fixes\n"));
1832
+ console.log(chalk7.cyan.bold("Run: ") + chalk7.white("edtools doctor --fix"));
1833
+ console.log(chalk7.cyan("To automatically apply suggested fixes\n"));
1517
1834
  }
1518
1835
  } else {
1519
- console.log(chalk6.green(" - Status: \u2705 Output directory correctly configured"));
1836
+ console.log(chalk7.green(" - Status: \u2705 Output directory correctly configured"));
1520
1837
  console.log("");
1521
1838
  }
1522
1839
  } 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)"));
1840
+ console.log(chalk7.gray("No hosting platform configuration detected"));
1841
+ console.log(chalk7.gray("(No firebase.json, vercel.json, netlify.toml, or amplify.yml found)"));
1525
1842
  console.log("");
1526
1843
  }
1527
- console.log(chalk6.cyan.bold("Summary:\n"));
1844
+ console.log(chalk7.cyan.bold("Summary:\n"));
1528
1845
  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"));
1846
+ console.log(chalk7.green.bold("\u2705 All checks passed! Your project is ready to generate content.\n"));
1530
1847
  } else {
1531
1848
  if (issues.length > 0) {
1532
- console.log(chalk6.red.bold(`\u274C ${issues.length} issue(s) found:`));
1849
+ console.log(chalk7.red.bold(`\u274C ${issues.length} issue(s) found:`));
1533
1850
  issues.forEach((issue) => {
1534
- console.log(chalk6.red(` - ${issue}`));
1851
+ console.log(chalk7.red(` - ${issue}`));
1535
1852
  });
1536
1853
  console.log("");
1537
1854
  }
1538
1855
  if (warnings.length > 0) {
1539
- console.log(chalk6.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1856
+ console.log(chalk7.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1540
1857
  warnings.forEach((warning) => {
1541
- console.log(chalk6.yellow(` - ${warning}`));
1858
+ console.log(chalk7.yellow(` - ${warning}`));
1542
1859
  });
1543
1860
  console.log("");
1544
1861
  }
1545
1862
  if (suggestions.length > 0 && !options.fix) {
1546
- console.log(chalk6.cyan.bold("\u{1F4A1} Suggestions:"));
1863
+ console.log(chalk7.cyan.bold("\u{1F4A1} Suggestions:"));
1547
1864
  suggestions.forEach((suggestion) => {
1548
- console.log(chalk6.cyan(` - ${suggestion}`));
1865
+ console.log(chalk7.cyan(` - ${suggestion}`));
1549
1866
  });
1550
1867
  console.log("");
1551
- console.log(chalk6.cyan("Run ") + chalk6.white("edtools doctor --fix") + chalk6.cyan(" to automatically apply fixes\n"));
1868
+ console.log(chalk7.cyan("Run ") + chalk7.white("edtools doctor --fix") + chalk7.cyan(" to automatically apply fixes\n"));
1552
1869
  }
1553
1870
  }
1554
1871
  }
1555
1872
 
1556
1873
  // src/cli/index.ts
1874
+ function getVersion2() {
1875
+ try {
1876
+ const __filename = fileURLToPath2(import.meta.url);
1877
+ const __dirname = path9.dirname(__filename);
1878
+ const possiblePaths = [
1879
+ path9.join(__dirname, "../../package.json"),
1880
+ // From dist/cli/
1881
+ path9.join(__dirname, "../../../package.json"),
1882
+ // From src/cli/
1883
+ path9.join(__dirname, "../../../../package.json")
1884
+ // From npm global install
1885
+ ];
1886
+ for (const pkgPath of possiblePaths) {
1887
+ if (fs11.existsSync(pkgPath)) {
1888
+ const pkg = fs11.readJsonSync(pkgPath);
1889
+ return pkg.version || "unknown";
1890
+ }
1891
+ }
1892
+ return "unknown";
1893
+ } catch (error) {
1894
+ return "dev";
1895
+ }
1896
+ }
1557
1897
  var program = new Command();
1558
- program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.2");
1898
+ program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version(getVersion2());
1559
1899
  program.command("init").description("Initialize edtools in your project").option("-p, --path <path>", "Project path", process.cwd()).action(initCommand);
1560
1900
  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);
1561
1901
  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
1902
  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
1903
  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
- });
1904
+ var configCmd = program.command("config").description("Manage edtools configuration");
1905
+ configCmd.command("show").description("Show current configuration status").option("-p, --path <path>", "Project path", process.cwd()).action(configShowCommand);
1906
+ 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
1907
  if (process.argv.length === 2) {
1573
1908
  showWelcome();
1574
1909
  process.exit(0);