@edtools/cli 0.6.0 → 0.6.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.
package/dist/cli/index.js CHANGED
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ContentGenerator
4
- } from "../chunk-JCUQ7D56.js";
4
+ } from "../chunk-BI3UJPWA.js";
5
5
  import "../chunk-INVECVSW.js";
6
6
 
7
7
  // src/cli/index.ts
8
8
  import { Command } from "commander";
9
- import chalk6 from "chalk";
9
+ import chalk7 from "chalk";
10
10
 
11
11
  // src/cli/commands/init.ts
12
- import fs from "fs-extra";
13
- import path from "path";
12
+ import fs2 from "fs-extra";
13
+ import path2 from "path";
14
14
  import chalk2 from "chalk";
15
15
  import ora from "ora";
16
16
  import inquirer from "inquirer";
@@ -104,12 +104,160 @@ function warningBox(message) {
104
104
  });
105
105
  }
106
106
 
107
+ // src/utils/hosting-detection.ts
108
+ import fs from "fs";
109
+ import path from "path";
110
+ import yaml from "yaml";
111
+ var detectors = [
112
+ {
113
+ name: "Firebase",
114
+ file: "firebase.json",
115
+ parse: (content, projectPath) => {
116
+ try {
117
+ const config = JSON.parse(content);
118
+ const publicDir = config.hosting?.public || "public";
119
+ return {
120
+ platform: "Firebase",
121
+ publicDir,
122
+ configFile: "firebase.json"
123
+ };
124
+ } catch (error) {
125
+ return null;
126
+ }
127
+ }
128
+ },
129
+ {
130
+ name: "AWS Amplify",
131
+ file: "amplify.yml",
132
+ parse: (content, projectPath) => {
133
+ try {
134
+ const config = yaml.parse(content);
135
+ const baseDirectory = config?.frontend?.artifacts?.baseDirectory || "build";
136
+ return {
137
+ platform: "AWS Amplify",
138
+ publicDir: baseDirectory,
139
+ configFile: "amplify.yml"
140
+ };
141
+ } catch (error) {
142
+ return null;
143
+ }
144
+ }
145
+ },
146
+ {
147
+ name: "Vercel",
148
+ file: "vercel.json",
149
+ parse: (content, projectPath) => {
150
+ try {
151
+ const config = JSON.parse(content);
152
+ const outputDirectory = config.outputDirectory || config.buildCommand?.match(/out|dist|build/)?.[0] || "public";
153
+ return {
154
+ platform: "Vercel",
155
+ publicDir: outputDirectory,
156
+ configFile: "vercel.json"
157
+ };
158
+ } catch (error) {
159
+ return null;
160
+ }
161
+ }
162
+ },
163
+ {
164
+ name: "Netlify",
165
+ file: "netlify.toml",
166
+ parse: (content, projectPath) => {
167
+ try {
168
+ const match = content.match(/publish\s*=\s*"([^"]+)"/);
169
+ const publicDir = match?.[1] || "public";
170
+ return {
171
+ platform: "Netlify",
172
+ publicDir,
173
+ configFile: "netlify.toml"
174
+ };
175
+ } catch (error) {
176
+ return null;
177
+ }
178
+ }
179
+ },
180
+ {
181
+ name: "Next.js",
182
+ file: "next.config.js",
183
+ parse: (content, projectPath) => {
184
+ 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"));
188
+ const buildScript = packageJson.scripts?.build || "";
189
+ if (buildScript.includes("out")) {
190
+ return {
191
+ platform: "Next.js (Static Export)",
192
+ publicDir: "out",
193
+ configFile: "next.config.js"
194
+ };
195
+ }
196
+ }
197
+ return {
198
+ platform: "Next.js (Static Export)",
199
+ publicDir: "out",
200
+ configFile: "next.config.js"
201
+ };
202
+ }
203
+ return null;
204
+ }
205
+ }
206
+ ];
207
+ function detectHostingConfig(projectPath) {
208
+ for (const detector of detectors) {
209
+ const configPath = path.join(projectPath, detector.file);
210
+ if (fs.existsSync(configPath)) {
211
+ try {
212
+ const content = fs.readFileSync(configPath, "utf-8");
213
+ const result = detector.parse(content, projectPath);
214
+ if (result) {
215
+ return result;
216
+ }
217
+ } catch (error) {
218
+ console.error(`Error parsing ${detector.file}:`, error);
219
+ }
220
+ }
221
+ }
222
+ return null;
223
+ }
224
+ function validateOutputDir(outputDir, hostingConfig) {
225
+ if (!hostingConfig) {
226
+ return { valid: true };
227
+ }
228
+ const normalizedOutputDir = outputDir.replace(/^\.\//, "");
229
+ const normalizedPublicDir = hostingConfig.publicDir.replace(/^\.\//, "");
230
+ const isInPublicDir = normalizedOutputDir.startsWith(`${normalizedPublicDir}/`) || normalizedOutputDir === normalizedPublicDir;
231
+ const issues = [];
232
+ if (!isInPublicDir) {
233
+ issues.push(
234
+ `Output directory "${outputDir}" is outside the public directory "${hostingConfig.publicDir}"`
235
+ );
236
+ issues.push("Generated files will not be accessible in production");
237
+ }
238
+ return {
239
+ valid: isInPublicDir,
240
+ platform: hostingConfig.platform,
241
+ publicDir: hostingConfig.publicDir,
242
+ currentOutputDir: outputDir,
243
+ suggestion: `./${normalizedPublicDir}/blog`,
244
+ issues: issues.length > 0 ? issues : void 0
245
+ };
246
+ }
247
+ function getSuggestedOutputDir(projectPath) {
248
+ const hostingConfig = detectHostingConfig(projectPath);
249
+ if (hostingConfig) {
250
+ return `./${hostingConfig.publicDir}/blog`;
251
+ }
252
+ return "./blog";
253
+ }
254
+
107
255
  // src/cli/commands/init.ts
108
256
  async function initCommand(options) {
109
257
  console.log(chalk2.cyan.bold("\n\u{1F680} Initializing Edtools...\n"));
110
- const projectPath = path.resolve(options.path);
111
- const configPath = path.join(projectPath, "edtools.config.js");
112
- if (await fs.pathExists(configPath)) {
258
+ const projectPath = path2.resolve(options.path);
259
+ const configPath = path2.join(projectPath, "edtools.config.js");
260
+ if (await fs2.pathExists(configPath)) {
113
261
  const { overwrite } = await inquirer.prompt([
114
262
  {
115
263
  type: "confirm",
@@ -126,9 +274,9 @@ async function initCommand(options) {
126
274
  const spinner = ora("Analyzing your landing page...").start();
127
275
  let productInfo = {};
128
276
  try {
129
- const indexPath = path.join(projectPath, "index.html");
130
- if (await fs.pathExists(indexPath)) {
131
- const html = await fs.readFile(indexPath, "utf-8");
277
+ const indexPath = path2.join(projectPath, "index.html");
278
+ if (await fs2.pathExists(indexPath)) {
279
+ const html = await fs2.readFile(indexPath, "utf-8");
132
280
  const $ = loadCheerio(html);
133
281
  productInfo = {
134
282
  name: $("title").text() || $("h1").first().text() || "My Product",
@@ -248,6 +396,15 @@ async function initCommand(options) {
248
396
  ...productInfo,
249
397
  ...answers
250
398
  };
399
+ const suggestedOutputDir = getSuggestedOutputDir(projectPath);
400
+ const hostingConfig = detectHostingConfig(projectPath);
401
+ if (hostingConfig) {
402
+ console.log("");
403
+ console.log(chalk2.cyan("\u2713 Detected hosting platform: ") + chalk2.white(hostingConfig.platform));
404
+ console.log(chalk2.cyan(" Public directory: ") + chalk2.white(hostingConfig.publicDir));
405
+ console.log(chalk2.cyan(" Suggested blog directory: ") + chalk2.white(suggestedOutputDir));
406
+ console.log("");
407
+ }
251
408
  const config = `module.exports = {
252
409
  product: {
253
410
  name: ${JSON.stringify(finalProductInfo.name)},
@@ -261,7 +418,7 @@ async function initCommand(options) {
261
418
  },
262
419
 
263
420
  content: {
264
- outputDir: './blog',
421
+ outputDir: ${JSON.stringify(suggestedOutputDir)},
265
422
  generateBlog: true,
266
423
  },
267
424
 
@@ -272,17 +429,17 @@ async function initCommand(options) {
272
429
  },
273
430
  };
274
431
  `;
275
- await fs.writeFile(configPath, config, "utf-8");
276
- const edtoolsDir = path.join(projectPath, ".edtools");
277
- await fs.ensureDir(edtoolsDir);
432
+ await fs2.writeFile(configPath, config, "utf-8");
433
+ const edtoolsDir = path2.join(projectPath, ".edtools");
434
+ await fs2.ensureDir(edtoolsDir);
278
435
  const edtoolsConfig = {
279
436
  apiKey: apiKeyAnswer.apiKey,
280
437
  provider: finalProductInfo.preferredProvider
281
438
  };
282
- const edtoolsConfigPath = path.join(edtoolsDir, "config.json");
283
- await fs.writeFile(edtoolsConfigPath, JSON.stringify(edtoolsConfig, null, 2), "utf-8");
284
- const gitignorePath = path.join(edtoolsDir, ".gitignore");
285
- await fs.writeFile(gitignorePath, "*\n!.gitignore\n", "utf-8");
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");
286
443
  console.log("");
287
444
  console.log(successBox("Configuration created successfully!"));
288
445
  const filesCreated = `${chalk2.cyan("Files created:")}
@@ -302,17 +459,18 @@ ${chalk2.gray("API key stored securely (gitignored)")}`;
302
459
  }
303
460
 
304
461
  // src/cli/commands/generate.ts
305
- import fs2 from "fs-extra";
306
- import path2 from "path";
462
+ import fs3 from "fs-extra";
463
+ import path3 from "path";
307
464
  import { pathToFileURL } from "url";
308
465
  import chalk3 from "chalk";
309
466
  import ora2 from "ora";
310
467
  import Table from "cli-table3";
468
+ import inquirer2 from "inquirer";
311
469
  async function generateCommand(options) {
312
470
  console.log(chalk3.cyan.bold("\n\u{1F4DD} Generating content...\n"));
313
471
  const projectPath = process.cwd();
314
- const configPath = path2.join(projectPath, "edtools.config.js");
315
- if (!await fs2.pathExists(configPath)) {
472
+ const configPath = path3.join(projectPath, "edtools.config.js");
473
+ if (!await fs3.pathExists(configPath)) {
316
474
  console.log(chalk3.red("\u2717 No edtools.config.js found"));
317
475
  console.log(chalk3.yellow(' Run "edtools init" first\n'));
318
476
  process.exit(1);
@@ -326,10 +484,10 @@ async function generateCommand(options) {
326
484
  }
327
485
  const provider = productInfo.preferredProvider || "anthropic";
328
486
  let storedApiKey;
329
- const edtoolsConfigPath = path2.join(projectPath, ".edtools", "config.json");
330
- if (await fs2.pathExists(edtoolsConfigPath)) {
487
+ const edtoolsConfigPath = path3.join(projectPath, ".edtools", "config.json");
488
+ if (await fs3.pathExists(edtoolsConfigPath)) {
331
489
  try {
332
- const edtoolsConfig = await fs2.readJson(edtoolsConfigPath);
490
+ const edtoolsConfig = await fs3.readJson(edtoolsConfigPath);
333
491
  storedApiKey = edtoolsConfig.apiKey;
334
492
  } catch (error) {
335
493
  }
@@ -356,14 +514,14 @@ async function generateCommand(options) {
356
514
  }
357
515
  let topics = options.topics;
358
516
  if (options.fromCsv) {
359
- const opportunitiesPath = path2.join(projectPath, ".edtools", "opportunities.json");
360
- if (!await fs2.pathExists(opportunitiesPath)) {
517
+ const opportunitiesPath = path3.join(projectPath, ".edtools", "opportunities.json");
518
+ if (!await fs3.pathExists(opportunitiesPath)) {
361
519
  console.log(chalk3.red("\u2717 No CSV analysis found"));
362
520
  console.log(chalk3.yellow(' Run "edtools analyze" first to analyze your GSC data\n'));
363
521
  process.exit(1);
364
522
  }
365
523
  try {
366
- const oppData = await fs2.readJson(opportunitiesPath);
524
+ const oppData = await fs3.readJson(opportunitiesPath);
367
525
  topics = oppData.opportunities.slice(0, parseInt(options.posts, 10)).map((opp) => opp.suggestedTitle);
368
526
  console.log(chalk3.cyan("\u{1F4CA} Using topics from CSV analysis:\n"));
369
527
  topics.forEach((topic, i) => {
@@ -384,8 +542,40 @@ async function generateCommand(options) {
384
542
  console.log(chalk3.yellow(`\u26A0\uFE0F Generating ${count} posts at once may trigger spam detection`));
385
543
  console.log(chalk3.yellow(" Recommended: 3-5 posts per week\n"));
386
544
  }
387
- const outputDir = path2.resolve(projectPath, options.output);
388
- await fs2.ensureDir(outputDir);
545
+ const outputDir = path3.resolve(projectPath, options.output);
546
+ await fs3.ensureDir(outputDir);
547
+ const hostingConfig = detectHostingConfig(projectPath);
548
+ if (hostingConfig) {
549
+ const validation = validateOutputDir(options.output, hostingConfig);
550
+ 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"));
555
+ 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"));
558
+ console.log("");
559
+ console.log(chalk3.cyan.bold("\u{1F4DD} Suggested fix:"));
560
+ console.log(chalk3.cyan(" Update edtools.config.js:"));
561
+ 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(" }"));
565
+ console.log("");
566
+ const answer = await inquirer2.prompt([{
567
+ type: "confirm",
568
+ name: "continue",
569
+ message: "Continue anyway?",
570
+ default: false
571
+ }]);
572
+ if (!answer.continue) {
573
+ console.log(chalk3.yellow("\n\u2717 Generation cancelled\n"));
574
+ process.exit(0);
575
+ }
576
+ console.log("");
577
+ }
578
+ }
389
579
  console.log(chalk3.cyan("Configuration:"));
390
580
  console.log(` Product: ${chalk3.white(productInfo.name)}`);
391
581
  console.log(` Category: ${chalk3.white(productInfo.category)}`);
@@ -543,17 +733,17 @@ function truncateTitle(title, maxLen) {
543
733
  }
544
734
 
545
735
  // src/cli/commands/analyze.ts
546
- import fs4 from "fs-extra";
547
- import path3 from "path";
736
+ import fs5 from "fs-extra";
737
+ import path4 from "path";
548
738
  import chalk4 from "chalk";
549
739
  import ora3 from "ora";
550
740
  import Table2 from "cli-table3";
551
741
 
552
742
  // src/integrations/gsc-csv-analyzer.ts
553
- import fs3 from "fs-extra";
743
+ import fs4 from "fs-extra";
554
744
  import { parse } from "csv-parse/sync";
555
745
  async function parseGSCCSV(filePath) {
556
- const content = await fs3.readFile(filePath, "utf-8");
746
+ const content = await fs4.readFile(filePath, "utf-8");
557
747
  const records = parse(content, {
558
748
  columns: true,
559
749
  skip_empty_lines: true,
@@ -649,7 +839,7 @@ async function saveOpportunities(opportunities, outputPath) {
649
839
  count: opportunities.length,
650
840
  opportunities
651
841
  };
652
- await fs3.writeJson(outputPath, data, { spaces: 2 });
842
+ await fs4.writeJson(outputPath, data, { spaces: 2 });
653
843
  }
654
844
 
655
845
  // src/cli/commands/analyze.ts
@@ -658,9 +848,9 @@ async function analyzeCommand(options) {
658
848
  const projectPath = process.cwd();
659
849
  let csvPath;
660
850
  if (options.file) {
661
- csvPath = path3.resolve(options.file);
851
+ csvPath = path4.resolve(options.file);
662
852
  } else {
663
- const files = await fs4.readdir(projectPath);
853
+ const files = await fs5.readdir(projectPath);
664
854
  const csvFiles = files.filter((f) => f.endsWith(".csv") && f.toLowerCase().includes("search"));
665
855
  if (csvFiles.length === 0) {
666
856
  console.log(chalk4.red("\u2717 No CSV file found"));
@@ -673,9 +863,9 @@ async function analyzeCommand(options) {
673
863
  csvFiles.forEach((f) => console.log(` - ${f}`));
674
864
  console.log(chalk4.yellow("\nUsing first file. Specify with --file to use a different one.\n"));
675
865
  }
676
- csvPath = path3.join(projectPath, csvFiles[0]);
866
+ csvPath = path4.join(projectPath, csvFiles[0]);
677
867
  }
678
- if (!await fs4.pathExists(csvPath)) {
868
+ if (!await fs5.pathExists(csvPath)) {
679
869
  console.log(chalk4.red(`\u2717 File not found: ${csvPath}
680
870
  `));
681
871
  process.exit(1);
@@ -684,7 +874,7 @@ async function analyzeCommand(options) {
684
874
  let data;
685
875
  try {
686
876
  data = await parseGSCCSV(csvPath);
687
- spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(path3.basename(csvPath))}`);
877
+ spinner.succeed(`Parsed ${chalk4.white(data.length)} keywords from ${chalk4.white(path4.basename(csvPath))}`);
688
878
  } catch (error) {
689
879
  spinner.fail("Failed to parse CSV");
690
880
  console.log(chalk4.red(`
@@ -789,9 +979,9 @@ Error: ${error.message}
789
979
  console.log(topTable.toString());
790
980
  console.log("");
791
981
  }
792
- const edtoolsDir = path3.join(projectPath, ".edtools");
793
- await fs4.ensureDir(edtoolsDir);
794
- const opportunitiesPath = path3.join(edtoolsDir, "opportunities.json");
982
+ const edtoolsDir = path4.join(projectPath, ".edtools");
983
+ await fs5.ensureDir(edtoolsDir);
984
+ const opportunitiesPath = path4.join(edtoolsDir, "opportunities.json");
795
985
  await saveOpportunities(analysis.opportunities, opportunitiesPath);
796
986
  console.log(successBox(`Analysis saved to ${chalk4.white(".edtools/opportunities.json")}`));
797
987
  console.log(chalk4.cyan.bold("Next steps:"));
@@ -802,16 +992,16 @@ Error: ${error.message}
802
992
  }
803
993
 
804
994
  // src/cli/commands/validate.ts
805
- import fs6 from "fs-extra";
806
- import path4 from "path";
995
+ import fs7 from "fs-extra";
996
+ import path5 from "path";
807
997
  import chalk5 from "chalk";
808
998
  import Table3 from "cli-table3";
809
999
 
810
1000
  // src/utils/seo-validator.ts
811
1001
  import * as cheerio from "cheerio";
812
- import fs5 from "fs-extra";
1002
+ import fs6 from "fs-extra";
813
1003
  async function validatePost(htmlPath) {
814
- const html = await fs5.readFile(htmlPath, "utf-8");
1004
+ const html = await fs6.readFile(htmlPath, "utf-8");
815
1005
  const $ = cheerio.load(html);
816
1006
  const issues = [];
817
1007
  const passed = [];
@@ -1038,15 +1228,15 @@ async function validateCommand(options) {
1038
1228
  const projectPath = process.cwd();
1039
1229
  let htmlFiles = [];
1040
1230
  if (options.post) {
1041
- const postPath = path4.resolve(projectPath, options.post);
1042
- if (!await fs6.pathExists(postPath)) {
1231
+ const postPath = path5.resolve(projectPath, options.post);
1232
+ if (!await fs7.pathExists(postPath)) {
1043
1233
  console.log(errorBox(`Post not found: ${postPath}`));
1044
1234
  process.exit(1);
1045
1235
  }
1046
1236
  htmlFiles = [postPath];
1047
1237
  } else if (options.posts) {
1048
- const postsDir = path4.resolve(projectPath, options.posts);
1049
- if (!await fs6.pathExists(postsDir)) {
1238
+ const postsDir = path5.resolve(projectPath, options.posts);
1239
+ if (!await fs7.pathExists(postsDir)) {
1050
1240
  console.log(errorBox(`Directory not found: ${postsDir}`));
1051
1241
  process.exit(1);
1052
1242
  }
@@ -1056,8 +1246,8 @@ async function validateCommand(options) {
1056
1246
  process.exit(1);
1057
1247
  }
1058
1248
  } else {
1059
- const defaultBlogDir = path4.join(projectPath, "blog");
1060
- if (await fs6.pathExists(defaultBlogDir)) {
1249
+ const defaultBlogDir = path5.join(projectPath, "blog");
1250
+ if (await fs7.pathExists(defaultBlogDir)) {
1061
1251
  htmlFiles = await findHtmlFiles(defaultBlogDir);
1062
1252
  if (htmlFiles.length === 0) {
1063
1253
  console.log(errorBox("No HTML files found in blog/ directory"));
@@ -1102,8 +1292,8 @@ async function validateCommand(options) {
1102
1292
  })),
1103
1293
  stats: stats2
1104
1294
  };
1105
- const outputPath = path4.resolve(projectPath, options.output);
1106
- await fs6.writeJson(outputPath, report, { spaces: 2 });
1295
+ const outputPath = path5.resolve(projectPath, options.output);
1296
+ await fs7.writeJson(outputPath, report, { spaces: 2 });
1107
1297
  console.log(successBox(`Validation report saved to ${outputPath}`));
1108
1298
  }
1109
1299
  if (filteredResults.length > 0) {
@@ -1181,9 +1371,9 @@ async function validateCommand(options) {
1181
1371
  }
1182
1372
  async function findHtmlFiles(dir) {
1183
1373
  const files = [];
1184
- const entries = await fs6.readdir(dir, { withFileTypes: true });
1374
+ const entries = await fs7.readdir(dir, { withFileTypes: true });
1185
1375
  for (const entry of entries) {
1186
- const fullPath = path4.join(dir, entry.name);
1376
+ const fullPath = path5.join(dir, entry.name);
1187
1377
  if (entry.isDirectory()) {
1188
1378
  const subFiles = await findHtmlFiles(fullPath);
1189
1379
  files.push(...subFiles);
@@ -1207,18 +1397,163 @@ function capitalize(str) {
1207
1397
  return str.charAt(0).toUpperCase() + str.slice(1);
1208
1398
  }
1209
1399
 
1400
+ // src/cli/commands/doctor.ts
1401
+ import fs8 from "fs-extra";
1402
+ import path6 from "path";
1403
+ import { pathToFileURL as pathToFileURL2 } from "url";
1404
+ import chalk6 from "chalk";
1405
+ async function doctorCommand(options) {
1406
+ console.log(chalk6.cyan.bold("\n\u{1F50D} Diagnosing project configuration...\n"));
1407
+ const projectPath = process.cwd();
1408
+ const configPath = path6.join(projectPath, "edtools.config.js");
1409
+ const issues = [];
1410
+ const warnings = [];
1411
+ const suggestions = [];
1412
+ if (await fs8.pathExists(configPath)) {
1413
+ console.log(chalk6.green("\u2713 Configuration file found: ") + chalk6.white("edtools.config.js"));
1414
+ } else {
1415
+ console.log(chalk6.red("\u2717 Configuration file not found"));
1416
+ console.log(chalk6.yellow(' Run "edtools init" to create configuration\n'));
1417
+ process.exit(1);
1418
+ }
1419
+ let productInfo;
1420
+ let outputDir;
1421
+ try {
1422
+ const configUrl = pathToFileURL2(configPath).href;
1423
+ const config = await import(configUrl);
1424
+ productInfo = config.default.product;
1425
+ outputDir = config.default.content?.outputDir || "./blog";
1426
+ if (!productInfo || !productInfo.name) {
1427
+ issues.push("Invalid product configuration in edtools.config.js");
1428
+ }
1429
+ } catch (error) {
1430
+ 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}
1433
+ `));
1434
+ process.exit(1);
1435
+ }
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));
1439
+ } else {
1440
+ 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"'));
1443
+ }
1444
+ const edtoolsConfigPath = path6.join(projectPath, ".edtools", "config.json");
1445
+ let hasApiKey = false;
1446
+ if (await fs8.pathExists(edtoolsConfigPath)) {
1447
+ try {
1448
+ const edtoolsConfig = await fs8.readJson(edtoolsConfigPath);
1449
+ hasApiKey = !!edtoolsConfig.apiKey;
1450
+ } catch (error) {
1451
+ }
1452
+ }
1453
+ const hasEnvApiKey = !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
1454
+ if (hasApiKey || hasEnvApiKey) {
1455
+ console.log(chalk6.green("\u2713 API key configured"));
1456
+ } else {
1457
+ 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'));
1461
+ }
1462
+ console.log("");
1463
+ const hostingConfig = detectHostingConfig(projectPath);
1464
+ 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));
1468
+ const validation = validateOutputDir(outputDir, hostingConfig);
1469
+ if (!validation.valid) {
1470
+ issues.push("Output directory misconfiguration");
1471
+ console.log(chalk6.red(" - Problem: Files outside ") + chalk6.white(hostingConfig.publicDir + "/") + chalk6.red(" won't be accessible"));
1472
+ 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"));
1475
+ console.log("");
1476
+ console.log(chalk6.cyan.bold("\u{1F4DD} Suggested fix:"));
1477
+ console.log(chalk6.cyan(" Update edtools.config.js:"));
1478
+ 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(" }"));
1482
+ console.log("");
1483
+ suggestions.push(`Update outputDir to '${validation.suggestion}'`);
1484
+ if (options.fix) {
1485
+ console.log(chalk6.yellow.bold("\u{1F527} Auto-fixing configuration...\n"));
1486
+ try {
1487
+ const configContent = await fs8.readFile(configPath, "utf-8");
1488
+ const updatedConfig = configContent.replace(
1489
+ /outputDir:\s*['"]([^'"]+)['"]/,
1490
+ `outputDir: '${validation.suggestion}'`
1491
+ );
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}'`));
1495
+ console.log("");
1496
+ console.log(chalk6.cyan('\u{1F389} Configuration fixed! Run "edtools generate" to create content.\n'));
1497
+ } 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"));
1501
+ }
1502
+ } else {
1503
+ console.log(chalk6.cyan.bold("Run: ") + chalk6.white("edtools doctor --fix"));
1504
+ console.log(chalk6.cyan("To automatically apply suggested fixes\n"));
1505
+ }
1506
+ } else {
1507
+ console.log(chalk6.green(" - Status: \u2705 Output directory correctly configured"));
1508
+ console.log("");
1509
+ }
1510
+ } 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)"));
1513
+ console.log("");
1514
+ }
1515
+ console.log(chalk6.cyan.bold("Summary:\n"));
1516
+ 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"));
1518
+ } else {
1519
+ if (issues.length > 0) {
1520
+ console.log(chalk6.red.bold(`\u274C ${issues.length} issue(s) found:`));
1521
+ issues.forEach((issue) => {
1522
+ console.log(chalk6.red(` - ${issue}`));
1523
+ });
1524
+ console.log("");
1525
+ }
1526
+ if (warnings.length > 0) {
1527
+ console.log(chalk6.yellow.bold(`\u26A0\uFE0F ${warnings.length} warning(s):`));
1528
+ warnings.forEach((warning) => {
1529
+ console.log(chalk6.yellow(` - ${warning}`));
1530
+ });
1531
+ console.log("");
1532
+ }
1533
+ if (suggestions.length > 0 && !options.fix) {
1534
+ console.log(chalk6.cyan.bold("\u{1F4A1} Suggestions:"));
1535
+ suggestions.forEach((suggestion) => {
1536
+ console.log(chalk6.cyan(` - ${suggestion}`));
1537
+ });
1538
+ console.log("");
1539
+ console.log(chalk6.cyan("Run ") + chalk6.white("edtools doctor --fix") + chalk6.cyan(" to automatically apply fixes\n"));
1540
+ }
1541
+ }
1542
+ }
1543
+
1210
1544
  // src/cli/index.ts
1211
1545
  var program = new Command();
1212
- program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.0");
1546
+ program.name("edtools").description("AI-Powered Content Marketing CLI - Generate, validate, and optimize SEO content").version("0.6.1");
1213
1547
  program.command("init").description("Initialize edtools in your project").option("-p, --path <path>", "Project path", process.cwd()).action(initCommand);
1214
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);
1215
1549
  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);
1216
1550
  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
+ program.command("doctor").description("Diagnose project configuration and hosting setup").option("--fix", "Automatically fix issues").action(doctorCommand);
1217
1552
  program.command("config").description("View or set configuration").option("--set-api-key <key>", "Set Anthropic API key").action(async (options) => {
1218
1553
  if (options.setApiKey) {
1219
- console.log(chalk6.green("\u2713 API key saved"));
1554
+ console.log(chalk7.green("\u2713 API key saved"));
1220
1555
  } else {
1221
- console.log(chalk6.cyan("Configuration:"));
1556
+ console.log(chalk7.cyan("Configuration:"));
1222
1557
  console.log(` API Key: ${process.env.ANTHROPIC_API_KEY ? "[set]" : "[not set]"}`);
1223
1558
  }
1224
1559
  });
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}