aidevops 2.52.1 → 2.53.2

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 (334) hide show
  1. package/README.md +1 -1
  2. package/VERSION +1 -1
  3. package/aidevops.sh +15 -9
  4. package/package.json +4 -4
  5. package/scripts/npm-postinstall.js +6 -7
  6. package/setup.sh +1 -1
  7. package/templates/deploy-templates.sh +144 -0
  8. package/templates/home/.agent/README.md +33 -0
  9. package/templates/home/AGENTS.md +96 -0
  10. package/templates/home/git/.agent/README.md +48 -0
  11. package/templates/home/git/AGENTS.md +97 -0
  12. package/templates/standard-functions.sh +179 -0
  13. package/templates/wordpress-performance-workflow.md +217 -0
  14. package/.agent/AGENTS.md +0 -614
  15. package/.agent/accounts.md +0 -65
  16. package/.agent/aidevops/add-new-mcp-to-aidevops.md +0 -456
  17. package/.agent/aidevops/api-integrations.md +0 -335
  18. package/.agent/aidevops/architecture.md +0 -510
  19. package/.agent/aidevops/configs.md +0 -274
  20. package/.agent/aidevops/docs.md +0 -244
  21. package/.agent/aidevops/extension.md +0 -311
  22. package/.agent/aidevops/mcp-integrations.md +0 -340
  23. package/.agent/aidevops/mcp-troubleshooting.md +0 -162
  24. package/.agent/aidevops/memory-patterns.md +0 -172
  25. package/.agent/aidevops/providers.md +0 -217
  26. package/.agent/aidevops/recommendations.md +0 -321
  27. package/.agent/aidevops/requirements.md +0 -301
  28. package/.agent/aidevops/resources.md +0 -214
  29. package/.agent/aidevops/security-requirements.md +0 -174
  30. package/.agent/aidevops/security.md +0 -350
  31. package/.agent/aidevops/service-links.md +0 -400
  32. package/.agent/aidevops/services.md +0 -357
  33. package/.agent/aidevops/setup.md +0 -153
  34. package/.agent/aidevops/troubleshooting.md +0 -389
  35. package/.agent/aidevops.md +0 -124
  36. package/.agent/build-plus.md +0 -244
  37. package/.agent/content/guidelines.md +0 -109
  38. package/.agent/content.md +0 -87
  39. package/.agent/health.md +0 -59
  40. package/.agent/legal.md +0 -59
  41. package/.agent/loop-state/full-loop.local.md +0 -16
  42. package/.agent/loop-state/ralph-loop.local.md +0 -10
  43. package/.agent/marketing.md +0 -440
  44. package/.agent/memory/README.md +0 -260
  45. package/.agent/onboarding.md +0 -796
  46. package/.agent/plan-plus.md +0 -245
  47. package/.agent/research.md +0 -100
  48. package/.agent/sales.md +0 -333
  49. package/.agent/scripts/101domains-helper.sh +0 -701
  50. package/.agent/scripts/add-missing-returns.sh +0 -140
  51. package/.agent/scripts/agent-browser-helper.sh +0 -311
  52. package/.agent/scripts/agno-setup.sh +0 -712
  53. package/.agent/scripts/ahrefs-mcp-wrapper.js +0 -168
  54. package/.agent/scripts/aidevops-update-check.sh +0 -71
  55. package/.agent/scripts/ampcode-cli.sh +0 -522
  56. package/.agent/scripts/auto-version-bump.sh +0 -156
  57. package/.agent/scripts/autogen-helper.sh +0 -512
  58. package/.agent/scripts/beads-sync-helper.sh +0 -596
  59. package/.agent/scripts/closte-helper.sh +0 -5
  60. package/.agent/scripts/cloudron-helper.sh +0 -321
  61. package/.agent/scripts/codacy-cli-chunked.sh +0 -581
  62. package/.agent/scripts/codacy-cli.sh +0 -442
  63. package/.agent/scripts/code-audit-helper.sh +0 -5
  64. package/.agent/scripts/coderabbit-cli.sh +0 -417
  65. package/.agent/scripts/coderabbit-pro-analysis.sh +0 -238
  66. package/.agent/scripts/commands/code-simplifier.md +0 -86
  67. package/.agent/scripts/commands/full-loop.md +0 -246
  68. package/.agent/scripts/commands/postflight-loop.md +0 -103
  69. package/.agent/scripts/commands/recall.md +0 -182
  70. package/.agent/scripts/commands/remember.md +0 -132
  71. package/.agent/scripts/commands/save-todo.md +0 -175
  72. package/.agent/scripts/commands/session-review.md +0 -154
  73. package/.agent/scripts/comprehensive-quality-fix.sh +0 -106
  74. package/.agent/scripts/context-builder-helper.sh +0 -522
  75. package/.agent/scripts/coolify-cli-helper.sh +0 -674
  76. package/.agent/scripts/coolify-helper.sh +0 -380
  77. package/.agent/scripts/crawl4ai-examples.sh +0 -401
  78. package/.agent/scripts/crawl4ai-helper.sh +0 -1078
  79. package/.agent/scripts/crewai-helper.sh +0 -681
  80. package/.agent/scripts/dev-browser-helper.sh +0 -513
  81. package/.agent/scripts/dns-helper.sh +0 -396
  82. package/.agent/scripts/domain-research-helper.sh +0 -917
  83. package/.agent/scripts/dspy-helper.sh +0 -285
  84. package/.agent/scripts/dspyground-helper.sh +0 -291
  85. package/.agent/scripts/eeat-score-helper.sh +0 -1242
  86. package/.agent/scripts/efficient-return-fix.sh +0 -92
  87. package/.agent/scripts/extract-opencode-prompts.sh +0 -128
  88. package/.agent/scripts/find-missing-returns.sh +0 -113
  89. package/.agent/scripts/fix-auth-headers.sh +0 -104
  90. package/.agent/scripts/fix-common-strings.sh +0 -254
  91. package/.agent/scripts/fix-content-type.sh +0 -100
  92. package/.agent/scripts/fix-error-messages.sh +0 -130
  93. package/.agent/scripts/fix-misplaced-returns.sh +0 -74
  94. package/.agent/scripts/fix-remaining-literals.sh +0 -152
  95. package/.agent/scripts/fix-return-statements.sh +0 -41
  96. package/.agent/scripts/fix-s131-default-cases.sh +0 -249
  97. package/.agent/scripts/fix-sc2155-simple.sh +0 -102
  98. package/.agent/scripts/fix-shellcheck-critical.sh +0 -187
  99. package/.agent/scripts/fix-string-literals.sh +0 -273
  100. package/.agent/scripts/full-loop-helper.sh +0 -773
  101. package/.agent/scripts/generate-opencode-agents.sh +0 -497
  102. package/.agent/scripts/generate-opencode-commands.sh +0 -1629
  103. package/.agent/scripts/generate-skills.sh +0 -366
  104. package/.agent/scripts/git-platforms-helper.sh +0 -640
  105. package/.agent/scripts/gitea-cli-helper.sh +0 -743
  106. package/.agent/scripts/github-cli-helper.sh +0 -702
  107. package/.agent/scripts/gitlab-cli-helper.sh +0 -682
  108. package/.agent/scripts/gsc-add-user-helper.sh +0 -325
  109. package/.agent/scripts/gsc-sitemap-helper.sh +0 -678
  110. package/.agent/scripts/hetzner-helper.sh +0 -485
  111. package/.agent/scripts/hostinger-helper.sh +0 -229
  112. package/.agent/scripts/keyword-research-helper.sh +0 -1815
  113. package/.agent/scripts/langflow-helper.sh +0 -544
  114. package/.agent/scripts/linkedin-automation.py +0 -241
  115. package/.agent/scripts/linter-manager.sh +0 -599
  116. package/.agent/scripts/linters-local.sh +0 -434
  117. package/.agent/scripts/list-keys-helper.sh +0 -488
  118. package/.agent/scripts/local-browser-automation.py +0 -339
  119. package/.agent/scripts/localhost-helper.sh +0 -744
  120. package/.agent/scripts/loop-common.sh +0 -806
  121. package/.agent/scripts/mainwp-helper.sh +0 -728
  122. package/.agent/scripts/markdown-formatter.sh +0 -338
  123. package/.agent/scripts/markdown-lint-fix.sh +0 -311
  124. package/.agent/scripts/mass-fix-returns.sh +0 -58
  125. package/.agent/scripts/mcp-diagnose.sh +0 -167
  126. package/.agent/scripts/mcp-inspector-helper.sh +0 -449
  127. package/.agent/scripts/memory-helper.sh +0 -650
  128. package/.agent/scripts/monitor-code-review.sh +0 -255
  129. package/.agent/scripts/onboarding-helper.sh +0 -706
  130. package/.agent/scripts/opencode-github-setup-helper.sh +0 -797
  131. package/.agent/scripts/opencode-test-helper.sh +0 -213
  132. package/.agent/scripts/pagespeed-helper.sh +0 -464
  133. package/.agent/scripts/pandoc-helper.sh +0 -362
  134. package/.agent/scripts/postflight-check.sh +0 -555
  135. package/.agent/scripts/pre-commit-hook.sh +0 -259
  136. package/.agent/scripts/pre-edit-check.sh +0 -169
  137. package/.agent/scripts/qlty-cli.sh +0 -356
  138. package/.agent/scripts/quality-cli-manager.sh +0 -525
  139. package/.agent/scripts/quality-feedback-helper.sh +0 -462
  140. package/.agent/scripts/quality-fix.sh +0 -263
  141. package/.agent/scripts/quality-loop-helper.sh +0 -1108
  142. package/.agent/scripts/ralph-loop-helper.sh +0 -836
  143. package/.agent/scripts/ralph-upstream-check.sh +0 -341
  144. package/.agent/scripts/secretlint-helper.sh +0 -847
  145. package/.agent/scripts/servers-helper.sh +0 -241
  146. package/.agent/scripts/ses-helper.sh +0 -619
  147. package/.agent/scripts/session-review-helper.sh +0 -404
  148. package/.agent/scripts/setup-linters-wizard.sh +0 -379
  149. package/.agent/scripts/setup-local-api-keys.sh +0 -330
  150. package/.agent/scripts/setup-mcp-integrations.sh +0 -472
  151. package/.agent/scripts/shared-constants.sh +0 -246
  152. package/.agent/scripts/site-crawler-helper.sh +0 -1487
  153. package/.agent/scripts/snyk-helper.sh +0 -940
  154. package/.agent/scripts/sonarcloud-autofix.sh +0 -193
  155. package/.agent/scripts/sonarcloud-cli.sh +0 -191
  156. package/.agent/scripts/sonarscanner-cli.sh +0 -455
  157. package/.agent/scripts/spaceship-helper.sh +0 -747
  158. package/.agent/scripts/stagehand-helper.sh +0 -321
  159. package/.agent/scripts/stagehand-python-helper.sh +0 -321
  160. package/.agent/scripts/stagehand-python-setup.sh +0 -441
  161. package/.agent/scripts/stagehand-setup.sh +0 -439
  162. package/.agent/scripts/system-cleanup.sh +0 -340
  163. package/.agent/scripts/terminal-title-helper.sh +0 -388
  164. package/.agent/scripts/terminal-title-setup.sh +0 -549
  165. package/.agent/scripts/test-stagehand-both-integration.sh +0 -317
  166. package/.agent/scripts/test-stagehand-integration.sh +0 -309
  167. package/.agent/scripts/test-stagehand-python-integration.sh +0 -341
  168. package/.agent/scripts/todo-ready.sh +0 -263
  169. package/.agent/scripts/tool-version-check.sh +0 -362
  170. package/.agent/scripts/toon-helper.sh +0 -469
  171. package/.agent/scripts/twilio-helper.sh +0 -917
  172. package/.agent/scripts/updown-helper.sh +0 -279
  173. package/.agent/scripts/validate-mcp-integrations.sh +0 -250
  174. package/.agent/scripts/validate-version-consistency.sh +0 -131
  175. package/.agent/scripts/vaultwarden-helper.sh +0 -597
  176. package/.agent/scripts/vercel-cli-helper.sh +0 -816
  177. package/.agent/scripts/verify-mirrors.sh +0 -169
  178. package/.agent/scripts/version-manager.sh +0 -831
  179. package/.agent/scripts/webhosting-helper.sh +0 -471
  180. package/.agent/scripts/webhosting-verify.sh +0 -238
  181. package/.agent/scripts/wordpress-mcp-helper.sh +0 -508
  182. package/.agent/scripts/worktree-helper.sh +0 -595
  183. package/.agent/scripts/worktree-sessions.sh +0 -577
  184. package/.agent/seo/dataforseo.md +0 -215
  185. package/.agent/seo/domain-research.md +0 -532
  186. package/.agent/seo/eeat-score.md +0 -659
  187. package/.agent/seo/google-search-console.md +0 -366
  188. package/.agent/seo/gsc-sitemaps.md +0 -282
  189. package/.agent/seo/keyword-research.md +0 -521
  190. package/.agent/seo/serper.md +0 -278
  191. package/.agent/seo/site-crawler.md +0 -387
  192. package/.agent/seo.md +0 -236
  193. package/.agent/services/accounting/quickfile.md +0 -159
  194. package/.agent/services/communications/telfon.md +0 -470
  195. package/.agent/services/communications/twilio.md +0 -569
  196. package/.agent/services/crm/fluentcrm.md +0 -449
  197. package/.agent/services/email/ses.md +0 -399
  198. package/.agent/services/hosting/101domains.md +0 -378
  199. package/.agent/services/hosting/closte.md +0 -177
  200. package/.agent/services/hosting/cloudflare.md +0 -251
  201. package/.agent/services/hosting/cloudron.md +0 -478
  202. package/.agent/services/hosting/dns-providers.md +0 -335
  203. package/.agent/services/hosting/domain-purchasing.md +0 -344
  204. package/.agent/services/hosting/hetzner.md +0 -327
  205. package/.agent/services/hosting/hostinger.md +0 -287
  206. package/.agent/services/hosting/localhost.md +0 -419
  207. package/.agent/services/hosting/spaceship.md +0 -353
  208. package/.agent/services/hosting/webhosting.md +0 -330
  209. package/.agent/social-media.md +0 -69
  210. package/.agent/templates/plans-template.md +0 -114
  211. package/.agent/templates/prd-template.md +0 -129
  212. package/.agent/templates/tasks-template.md +0 -108
  213. package/.agent/templates/todo-template.md +0 -89
  214. package/.agent/tools/ai-assistants/agno.md +0 -471
  215. package/.agent/tools/ai-assistants/capsolver.md +0 -326
  216. package/.agent/tools/ai-assistants/configuration.md +0 -221
  217. package/.agent/tools/ai-assistants/overview.md +0 -209
  218. package/.agent/tools/ai-assistants/status.md +0 -171
  219. package/.agent/tools/ai-assistants/windsurf.md +0 -193
  220. package/.agent/tools/ai-orchestration/autogen.md +0 -406
  221. package/.agent/tools/ai-orchestration/crewai.md +0 -445
  222. package/.agent/tools/ai-orchestration/langflow.md +0 -405
  223. package/.agent/tools/ai-orchestration/openprose.md +0 -487
  224. package/.agent/tools/ai-orchestration/overview.md +0 -362
  225. package/.agent/tools/ai-orchestration/packaging.md +0 -647
  226. package/.agent/tools/browser/agent-browser.md +0 -464
  227. package/.agent/tools/browser/browser-automation.md +0 -400
  228. package/.agent/tools/browser/chrome-devtools.md +0 -282
  229. package/.agent/tools/browser/crawl4ai-integration.md +0 -422
  230. package/.agent/tools/browser/crawl4ai-resources.md +0 -277
  231. package/.agent/tools/browser/crawl4ai-usage.md +0 -416
  232. package/.agent/tools/browser/crawl4ai.md +0 -585
  233. package/.agent/tools/browser/dev-browser.md +0 -341
  234. package/.agent/tools/browser/pagespeed.md +0 -260
  235. package/.agent/tools/browser/playwright.md +0 -266
  236. package/.agent/tools/browser/playwriter.md +0 -310
  237. package/.agent/tools/browser/stagehand-examples.md +0 -456
  238. package/.agent/tools/browser/stagehand-python.md +0 -483
  239. package/.agent/tools/browser/stagehand.md +0 -421
  240. package/.agent/tools/build-agent/agent-review.md +0 -224
  241. package/.agent/tools/build-agent/build-agent.md +0 -784
  242. package/.agent/tools/build-mcp/aidevops-plugin.md +0 -476
  243. package/.agent/tools/build-mcp/api-wrapper.md +0 -445
  244. package/.agent/tools/build-mcp/build-mcp.md +0 -240
  245. package/.agent/tools/build-mcp/deployment.md +0 -401
  246. package/.agent/tools/build-mcp/server-patterns.md +0 -632
  247. package/.agent/tools/build-mcp/transports.md +0 -366
  248. package/.agent/tools/code-review/auditing.md +0 -383
  249. package/.agent/tools/code-review/automation.md +0 -219
  250. package/.agent/tools/code-review/best-practices.md +0 -203
  251. package/.agent/tools/code-review/codacy.md +0 -151
  252. package/.agent/tools/code-review/code-simplifier.md +0 -174
  253. package/.agent/tools/code-review/code-standards.md +0 -309
  254. package/.agent/tools/code-review/coderabbit.md +0 -101
  255. package/.agent/tools/code-review/management.md +0 -155
  256. package/.agent/tools/code-review/qlty.md +0 -248
  257. package/.agent/tools/code-review/secretlint.md +0 -565
  258. package/.agent/tools/code-review/setup.md +0 -250
  259. package/.agent/tools/code-review/snyk.md +0 -563
  260. package/.agent/tools/code-review/tools.md +0 -230
  261. package/.agent/tools/content/summarize.md +0 -353
  262. package/.agent/tools/context/augment-context-engine.md +0 -468
  263. package/.agent/tools/context/context-builder-agent.md +0 -76
  264. package/.agent/tools/context/context-builder.md +0 -375
  265. package/.agent/tools/context/context7.md +0 -371
  266. package/.agent/tools/context/dspy.md +0 -302
  267. package/.agent/tools/context/dspyground.md +0 -374
  268. package/.agent/tools/context/llm-tldr.md +0 -219
  269. package/.agent/tools/context/osgrep.md +0 -488
  270. package/.agent/tools/context/prompt-optimization.md +0 -338
  271. package/.agent/tools/context/toon.md +0 -292
  272. package/.agent/tools/conversion/pandoc.md +0 -304
  273. package/.agent/tools/credentials/api-key-management.md +0 -154
  274. package/.agent/tools/credentials/api-key-setup.md +0 -224
  275. package/.agent/tools/credentials/environment-variables.md +0 -180
  276. package/.agent/tools/credentials/vaultwarden.md +0 -382
  277. package/.agent/tools/data-extraction/outscraper.md +0 -974
  278. package/.agent/tools/deployment/coolify-cli.md +0 -388
  279. package/.agent/tools/deployment/coolify-setup.md +0 -353
  280. package/.agent/tools/deployment/coolify.md +0 -345
  281. package/.agent/tools/deployment/vercel.md +0 -390
  282. package/.agent/tools/git/authentication.md +0 -132
  283. package/.agent/tools/git/gitea-cli.md +0 -193
  284. package/.agent/tools/git/github-actions.md +0 -207
  285. package/.agent/tools/git/github-cli.md +0 -223
  286. package/.agent/tools/git/gitlab-cli.md +0 -190
  287. package/.agent/tools/git/opencode-github-security.md +0 -350
  288. package/.agent/tools/git/opencode-github.md +0 -328
  289. package/.agent/tools/git/opencode-gitlab.md +0 -252
  290. package/.agent/tools/git/security.md +0 -196
  291. package/.agent/tools/git.md +0 -207
  292. package/.agent/tools/opencode/oh-my-opencode.md +0 -375
  293. package/.agent/tools/opencode/opencode-anthropic-auth.md +0 -446
  294. package/.agent/tools/opencode/opencode.md +0 -651
  295. package/.agent/tools/social-media/bird.md +0 -437
  296. package/.agent/tools/task-management/beads.md +0 -336
  297. package/.agent/tools/terminal/terminal-title.md +0 -251
  298. package/.agent/tools/ui/shadcn.md +0 -196
  299. package/.agent/tools/ui/ui-skills.md +0 -115
  300. package/.agent/tools/wordpress/localwp.md +0 -311
  301. package/.agent/tools/wordpress/mainwp.md +0 -391
  302. package/.agent/tools/wordpress/scf.md +0 -527
  303. package/.agent/tools/wordpress/wp-admin.md +0 -729
  304. package/.agent/tools/wordpress/wp-dev.md +0 -940
  305. package/.agent/tools/wordpress/wp-preferred.md +0 -398
  306. package/.agent/tools/wordpress.md +0 -95
  307. package/.agent/workflows/branch/bugfix.md +0 -63
  308. package/.agent/workflows/branch/chore.md +0 -95
  309. package/.agent/workflows/branch/experiment.md +0 -115
  310. package/.agent/workflows/branch/feature.md +0 -59
  311. package/.agent/workflows/branch/hotfix.md +0 -98
  312. package/.agent/workflows/branch/refactor.md +0 -92
  313. package/.agent/workflows/branch/release.md +0 -96
  314. package/.agent/workflows/branch.md +0 -347
  315. package/.agent/workflows/bug-fixing.md +0 -267
  316. package/.agent/workflows/changelog.md +0 -129
  317. package/.agent/workflows/code-audit-remote.md +0 -279
  318. package/.agent/workflows/conversation-starter.md +0 -69
  319. package/.agent/workflows/error-feedback.md +0 -578
  320. package/.agent/workflows/feature-development.md +0 -355
  321. package/.agent/workflows/git-workflow.md +0 -702
  322. package/.agent/workflows/multi-repo-workspace.md +0 -268
  323. package/.agent/workflows/plans.md +0 -709
  324. package/.agent/workflows/postflight.md +0 -604
  325. package/.agent/workflows/pr.md +0 -571
  326. package/.agent/workflows/preflight.md +0 -278
  327. package/.agent/workflows/ralph-loop.md +0 -773
  328. package/.agent/workflows/release.md +0 -498
  329. package/.agent/workflows/session-manager.md +0 -254
  330. package/.agent/workflows/session-review.md +0 -311
  331. package/.agent/workflows/sql-migrations.md +0 -631
  332. package/.agent/workflows/version-bump.md +0 -283
  333. package/.agent/workflows/wiki-update.md +0 -333
  334. package/.agent/workflows/worktree.md +0 -477
@@ -1,1242 +0,0 @@
1
- #!/bin/bash
2
- # shellcheck disable=SC2034,SC2155,SC2317,SC2329,SC2016,SC2181,SC1091,SC2154,SC2015,SC2086,SC2129,SC2030,SC2031,SC2119,SC2120,SC2001,SC2162,SC2088,SC2089,SC2090,SC2029,SC2006,SC2153
3
-
4
- # E-E-A-T Score Helper Script
5
- # Content quality scoring using Google's E-E-A-T framework
6
- #
7
- # Usage: ./eeat-score-helper.sh [command] [input] [options]
8
- # Commands:
9
- # analyze - Analyze crawled pages from site-crawler output
10
- # score - Score a single URL
11
- # batch - Batch analyze URLs from a file
12
- # report - Generate spreadsheet from existing scores
13
- # status - Check dependencies and configuration
14
- # help - Show this help message
15
- #
16
- # Author: AI DevOps Framework
17
- # Version: 1.0.0
18
- # License: MIT
19
-
20
- set -euo pipefail
21
-
22
- # Colors for output
23
- readonly GREEN='\033[0;32m'
24
- readonly BLUE='\033[0;34m'
25
- readonly YELLOW='\033[1;33m'
26
- readonly RED='\033[0;31m'
27
- readonly PURPLE='\033[0;35m'
28
- readonly NC='\033[0m'
29
-
30
- # Constants
31
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" || exit
32
- readonly SCRIPT_DIR
33
- readonly CONFIG_DIR="${HOME}/.config/aidevops"
34
- readonly CONFIG_FILE="${CONFIG_DIR}/eeat-score.json"
35
- readonly DEFAULT_OUTPUT_DIR="${HOME}/Downloads"
36
-
37
- # Default configuration
38
- LLM_PROVIDER="openai"
39
- LLM_MODEL="gpt-4o"
40
- TEMPERATURE="0.3"
41
- MAX_TOKENS="500"
42
- CONCURRENT_REQUESTS=3
43
- OUTPUT_FORMAT="xlsx"
44
- INCLUDE_REASONING=true
45
-
46
- # Weights for overall score calculation
47
- WEIGHT_AUTHORSHIP=0.15
48
- WEIGHT_CITATION=0.15
49
- WEIGHT_EFFORT=0.15
50
- WEIGHT_ORIGINALITY=0.15
51
- WEIGHT_INTENT=0.15
52
- WEIGHT_SUBJECTIVE=0.15
53
- WEIGHT_WRITING=0.10
54
-
55
- # Print functions
56
- print_success() {
57
- local message="$1"
58
- echo -e "${GREEN}[OK] $message${NC}"
59
- return 0
60
- }
61
-
62
- print_info() {
63
- local message="$1"
64
- echo -e "${BLUE}[INFO] $message${NC}"
65
- return 0
66
- }
67
-
68
- print_warning() {
69
- local message="$1"
70
- echo -e "${YELLOW}[WARN] $message${NC}"
71
- return 0
72
- }
73
-
74
- print_error() {
75
- local message="$1"
76
- echo -e "${RED}[ERROR] $message${NC}"
77
- return 0
78
- }
79
-
80
- print_header() {
81
- local message="$1"
82
- echo -e "${PURPLE}=== $message ===${NC}"
83
- return 0
84
- }
85
-
86
- # Load configuration
87
- load_config() {
88
- if [[ -f "$CONFIG_FILE" ]]; then
89
- if command -v jq &> /dev/null; then
90
- LLM_PROVIDER=$(jq -r '.llm_provider // "openai"' "$CONFIG_FILE")
91
- LLM_MODEL=$(jq -r '.llm_model // "gpt-4o"' "$CONFIG_FILE")
92
- TEMPERATURE=$(jq -r '.temperature // 0.3' "$CONFIG_FILE")
93
- MAX_TOKENS=$(jq -r '.max_tokens // 500' "$CONFIG_FILE")
94
- CONCURRENT_REQUESTS=$(jq -r '.concurrent_requests // 3' "$CONFIG_FILE")
95
- OUTPUT_FORMAT=$(jq -r '.output_format // "xlsx"' "$CONFIG_FILE")
96
- INCLUDE_REASONING=$(jq -r '.include_reasoning // true' "$CONFIG_FILE")
97
-
98
- # Load weights
99
- WEIGHT_AUTHORSHIP=$(jq -r '.weights.authorship // 0.15' "$CONFIG_FILE")
100
- WEIGHT_CITATION=$(jq -r '.weights.citation // 0.15' "$CONFIG_FILE")
101
- WEIGHT_EFFORT=$(jq -r '.weights.effort // 0.15' "$CONFIG_FILE")
102
- WEIGHT_ORIGINALITY=$(jq -r '.weights.originality // 0.15' "$CONFIG_FILE")
103
- WEIGHT_INTENT=$(jq -r '.weights.intent // 0.15' "$CONFIG_FILE")
104
- WEIGHT_SUBJECTIVE=$(jq -r '.weights.subjective // 0.15' "$CONFIG_FILE")
105
- WEIGHT_WRITING=$(jq -r '.weights.writing // 0.10' "$CONFIG_FILE")
106
- fi
107
- fi
108
- return 0
109
- }
110
-
111
- # Check dependencies
112
- check_dependencies() {
113
- local missing=()
114
-
115
- if ! command -v curl &> /dev/null; then
116
- missing+=("curl")
117
- fi
118
-
119
- if ! command -v jq &> /dev/null; then
120
- missing+=("jq")
121
- fi
122
-
123
- if ! command -v python3 &> /dev/null; then
124
- missing+=("python3")
125
- fi
126
-
127
- if [[ ${#missing[@]} -gt 0 ]]; then
128
- print_error "Missing dependencies: ${missing[*]}"
129
- print_info "Install with: brew install ${missing[*]}"
130
- return 1
131
- fi
132
-
133
- return 0
134
- }
135
-
136
- # Check API key
137
- check_api_key() {
138
- if [[ "$LLM_PROVIDER" == "openai" ]]; then
139
- if [[ -z "${OPENAI_API_KEY:-}" ]]; then
140
- print_error "OPENAI_API_KEY environment variable not set"
141
- print_info "Set with: export OPENAI_API_KEY='sk-...'"
142
- return 1
143
- fi
144
- elif [[ "$LLM_PROVIDER" == "anthropic" ]]; then
145
- if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
146
- print_error "ANTHROPIC_API_KEY environment variable not set"
147
- return 1
148
- fi
149
- fi
150
- return 0
151
- }
152
-
153
- # Extract domain from URL
154
- get_domain() {
155
- local url="$1"
156
- echo "$url" | sed -E 's|^https?://||' | sed -E 's|/.*||' | sed -E 's|:.*||'
157
- }
158
-
159
- # Create output directory structure
160
- create_output_dir() {
161
- local domain="$1"
162
- local output_base="${2:-$DEFAULT_OUTPUT_DIR}"
163
- local timestamp
164
- timestamp=$(date +%Y-%m-%d_%H%M%S)
165
-
166
- local output_dir="${output_base}/${domain}/${timestamp}"
167
- mkdir -p "$output_dir"
168
-
169
- # Update _latest symlink
170
- local latest_link="${output_base}/${domain}/_latest"
171
- rm -f "$latest_link"
172
- ln -sf "$timestamp" "$latest_link"
173
-
174
- echo "$output_dir"
175
- return 0
176
- }
177
-
178
- # Generate Python E-E-A-T analyzer script
179
- generate_analyzer_script() {
180
- cat << 'PYTHON_SCRIPT'
181
- #!/usr/bin/env python3
182
- """
183
- E-E-A-T Score Analyzer
184
- Evaluates content quality using Google's E-E-A-T framework
185
- """
186
-
187
- import asyncio
188
- import json
189
- import csv
190
- import sys
191
- import os
192
- from datetime import datetime
193
- from pathlib import Path
194
- from dataclasses import dataclass, field, asdict
195
- from typing import Optional, List, Dict
196
- import aiohttp
197
- from bs4 import BeautifulSoup
198
-
199
- try:
200
- import openpyxl
201
- from openpyxl.styles import Font, PatternFill, Alignment
202
- HAS_OPENPYXL = True
203
- except ImportError:
204
- HAS_OPENPYXL = False
205
-
206
- # E-E-A-T Prompts
207
- PROMPTS = {
208
- "authorship_reasoning": """You are evaluating Authorship & Expertise for this page. Analyze and explain in 3-4 sentences:
209
- - Is there a clear AUTHOR? If yes, who and what credentials?
210
- - Can you identify the PUBLISHER (who owns/operates the site)?
211
- - Is this a "Disconnected Entity" (anonymous, untraceable) or "Connected Entity" (verifiable)?
212
- - Do they demonstrate RELEVANT EXPERTISE for this topic?
213
- Be specific with names, credentials, evidence from the page.""",
214
-
215
- "authorship_score": """You are evaluating Authorship & Expertise (isAuthor criterion).
216
- CRITICAL: A "Disconnected Entity" is one where you CANNOT find "who owns and operates" the site.
217
- Evaluate:
218
- - Is there a clear author byline linking to a detailed biography?
219
- - Does the About page clearly identify the company or person responsible?
220
- - Is this entity VERIFIABLE and ACCOUNTABLE?
221
- - Do they demonstrate RELEVANT EXPERTISE for this topic?
222
- Score 1-10:
223
- 1-3 = DISCONNECTED ENTITY: No clear author, anonymous, untraceable
224
- 4-6 = Partial attribution, but weak verifiability or unclear credentials
225
- 7-10 = CONNECTED ENTITY: Clear author with detailed bio, verifiable expertise
226
- Return ONLY the number.""",
227
-
228
- "citation_reasoning": """You are evaluating Citation Quality for this page. Analyze and explain in 3-4 sentences:
229
- - Does the page make SPECIFIC FACTUAL CLAIMS?
230
- - Are those claims SUBSTANTIATED with citations?
231
- - QUALITY assessment: Primary sources (studies, official docs) or secondary/low-quality?
232
- - Or are claims unsupported?
233
- Be specific with examples of claims and their (lack of) citations.""",
234
-
235
- "citation_score": """You are evaluating Citation Quality & Substantiation.
236
- Does this content BACK UP its claims with high-quality sources?
237
- Analyze:
238
- - Does the page make SPECIFIC FACTUAL CLAIMS?
239
- - Are those claims SUBSTANTIATED with citations/links?
240
- - QUALITY of sources: Primary sources (studies, legal docs, official data)?
241
- Score 1-10:
242
- 1-3 = LOW: Bold claims with NO citations, or only low-quality links
243
- 4-6 = MODERATE: Some citations but mediocre quality
244
- 7-10 = HIGH: Core claims substantiated with primary sources
245
- Return ONLY the number.""",
246
-
247
- "effort_reasoning": """You are evaluating Content Effort for this page. Analyze and explain in 3-4 sentences:
248
- - How DIFFICULT would it be to REPLICATE this content? (time, cost, expertise)
249
- - Does the page "SHOW ITS WORK"? Is the creation process transparent?
250
- - What evidence of high/low effort? (original research, data, multimedia, depth)
251
- - Any unique elements that required significant resources?
252
- Be specific with examples from the page.""",
253
-
254
- "effort_score": """You are evaluating Content Effort.
255
- Assess the DEMONSTRABLE effort, expertise, and resources invested.
256
- Key questions:
257
- 1. REPLICABILITY: How difficult would it be for a competitor to create equal content?
258
- 2. CREATION PROCESS: Does the page "show its work"?
259
- Look for: In-depth analysis, original data, unique multimedia, transparent methodology
260
- Score 1-10:
261
- 1-3 = LOW EFFORT: Generic, formulaic, easily replicated in hours
262
- 7-8 = HIGH EFFORT: Significant investment, hard to replicate
263
- 9-10 = EXCEPTIONAL: Original research, proprietary data, unique tools
264
- Return ONLY the number.""",
265
-
266
- "originality_reasoning": """You are evaluating Content Originality for this page. Analyze and explain in 3-4 sentences:
267
- - Does this page introduce NEW INFORMATION or a UNIQUE PERSPECTIVE?
268
- - Or does it just REPHRASE existing knowledge from other sources?
269
- - Is it substantively unique in phrasing, data, angle, or presentation?
270
- - What makes it original or generic?
271
- Be specific with examples.""",
272
-
273
- "originality_score": """You are evaluating Content Originality.
274
- Does this content ADD NEW INFORMATION to the web, or just rephrase what exists?
275
- Evaluate:
276
- - Is the content SUBSTANTIVELY UNIQUE in phrasing, perspective, data?
277
- - Does it introduce NEW INFORMATION or a UNIQUE ANGLE?
278
- Red flags: Templated content, spun/paraphrased, generic information
279
- Score 1-10:
280
- 1-3 = LOW ORIGINALITY: Templated, duplicated, rehashes existing knowledge
281
- 4-6 = MODERATE: Mix of original and generic elements
282
- 7-10 = HIGH ORIGINALITY: Substantively unique, adds new information
283
- Return ONLY the number.""",
284
-
285
- "intent_reasoning": """You are evaluating Page Intent for this page. Analyze and explain in 3-4 sentences:
286
- - What is this page's PRIMARY PURPOSE (the "WHY" it exists)?
287
- - Is it HELPFUL-FIRST (created to help users) or SEARCH-FIRST (created to rank)?
288
- - Is the intent TRANSPARENT and honest, or DECEPTIVE?
289
- - What evidence supports your assessment?
290
- Be specific with examples from the content.""",
291
-
292
- "intent_score": """You are evaluating Page Intent.
293
- WHY was this page created? What is its PRIMARY PURPOSE?
294
- Determine if this is:
295
- - HELPFUL-FIRST: Created primarily to help users/solve problems
296
- - Or SEARCH-FIRST: Created primarily to rank in search
297
- Red flags: Thin content for keywords, disguised affiliate, keyword stuffing
298
- Green flags: Clear user problem solved, transparent purpose, genuine value
299
- Score 1-10:
300
- 1-3 = DECEPTIVE/SEARCH-FIRST: Created for search traffic, deceptive intent
301
- 4-6 = UNCLEAR: Mixed signals
302
- 7-10 = TRANSPARENT/HELPFUL-FIRST: Created to help people, honest purpose
303
- Return ONLY the number.""",
304
-
305
- "subjective_reasoning": """You are a brutally honest content critic. Be direct, not nice. Evaluate this content for:
306
- boring sections, confusing parts, unbelievable claims, unclear audience pain point,
307
- missing culprit identification, sections that could be condensed, lack of proprietary insights.
308
- CRITICAL: Provide EXACTLY 2-3 sentences summarizing the main weaknesses.
309
- NO bullet points. NO lists. NO section headers. NO more than 3 sentences.""",
310
-
311
- "subjective_score": """You are a brutally honest content critic evaluating subjective quality.
312
- CRITICAL: Put on your most critical hat. Don't be nice. High standards only.
313
- Evaluate: ENGAGEMENT (boring or compelling?), CLARITY (confusing?), CREDIBILITY (believable?),
314
- AUDIENCE TARGETING (pain point addressed?), VALUE DENSITY (fluff or substance?)
315
- Score 1-10:
316
- 1-3 = LOW QUALITY: Boring, confusing, unbelievable, generic advice
317
- 4-6 = MEDIOCRE: Some good parts but significant issues
318
- 7-10 = HIGH QUALITY: Compelling, clear, credible, dense value
319
- Return ONLY the number.""",
320
-
321
- "writing_reasoning": """You are a writing quality analyst. Evaluate this text's linguistic quality.
322
- Analyze: lexical diversity (vocabulary richness/repetition), readability (sentence length 15-20 words optimal),
323
- modal verbs balance, passive voice usage, and heavy adverbs.
324
- CRITICAL: Provide EXACTLY 2-3 sentences summarizing the main writing issues.
325
- NO bullet points. NO lists. Maximum 150 words total.""",
326
-
327
- "writing_score": """You are a writing quality analyst evaluating objective linguistic metrics.
328
- Analyze:
329
- 1. LEXICAL DIVERSITY: Rich vocabulary or repetitive?
330
- 2. READABILITY: Sentence length 15-20 words optimal, mix of easy/medium sentences
331
- 3. LINGUISTIC QUALITY: Modal verbs balanced, minimal passive voice, limited heavy adverbs
332
- Score 1-10:
333
- 1-3 = POOR: Repetitive vocabulary, long complex sentences, excessive passive/adverbs
334
- 4-6 = AVERAGE: Some issues with readability or linguistic quality
335
- 7-10 = EXCELLENT: Rich vocabulary, optimal sentence length, active voice, concise
336
- Return ONLY the number."""
337
- }
338
-
339
- @dataclass
340
- class EEATScore:
341
- url: str
342
- authorship_score: int = 0
343
- authorship_reasoning: str = ""
344
- citation_score: int = 0
345
- citation_reasoning: str = ""
346
- effort_score: int = 0
347
- effort_reasoning: str = ""
348
- originality_score: int = 0
349
- originality_reasoning: str = ""
350
- intent_score: int = 0
351
- intent_reasoning: str = ""
352
- subjective_score: int = 0
353
- subjective_reasoning: str = ""
354
- writing_score: int = 0
355
- writing_reasoning: str = ""
356
- overall_score: float = 0.0
357
- grade: str = ""
358
- analyzed_at: str = ""
359
-
360
- class EEATAnalyzer:
361
- def __init__(self, output_dir: str, provider: str = "openai",
362
- model: str = "gpt-4o", temperature: float = 0.3,
363
- weights: Dict[str, float] = None):
364
- self.output_dir = Path(output_dir)
365
- self.provider = provider
366
- self.model = model
367
- self.temperature = temperature
368
- self.weights = weights or {
369
- "authorship": 0.15,
370
- "citation": 0.15,
371
- "effort": 0.15,
372
- "originality": 0.15,
373
- "intent": 0.15,
374
- "subjective": 0.15,
375
- "writing": 0.10
376
- }
377
- self.session: Optional[aiohttp.ClientSession] = None
378
- self.scores: List[EEATScore] = []
379
-
380
- async def fetch_page_content(self, url: str) -> str:
381
- """Fetch page content for analysis"""
382
- try:
383
- async with self.session.get(url, timeout=30) as response:
384
- if response.status == 200:
385
- html = await response.text()
386
- soup = BeautifulSoup(html, 'html.parser')
387
-
388
- # Remove script and style elements
389
- for element in soup(['script', 'style', 'nav', 'footer', 'header']):
390
- element.decompose()
391
-
392
- # Get text content
393
- text = soup.get_text(separator='\n', strip=True)
394
-
395
- # Truncate to reasonable length for LLM
396
- if len(text) > 15000:
397
- text = text[:15000] + "\n[Content truncated...]"
398
-
399
- return text
400
- except Exception as e:
401
- print(f"Error fetching {url}: {e}")
402
- return ""
403
-
404
- async def call_llm(self, prompt: str, content: str) -> str:
405
- """Call LLM API for analysis"""
406
- if self.provider == "openai":
407
- return await self._call_openai(prompt, content)
408
- elif self.provider == "anthropic":
409
- return await self._call_anthropic(prompt, content)
410
- return ""
411
-
412
- async def _call_openai(self, prompt: str, content: str) -> str:
413
- """Call OpenAI API"""
414
- api_key = os.environ.get("OPENAI_API_KEY")
415
- if not api_key:
416
- raise ValueError("OPENAI_API_KEY not set")
417
-
418
- headers = {
419
- "Authorization": f"Bearer {api_key}",
420
- "Content-Type": "application/json"
421
- }
422
-
423
- payload = {
424
- "model": self.model,
425
- "messages": [
426
- {"role": "system", "content": prompt},
427
- {"role": "user", "content": f"Analyze this content:\n\n{content}"}
428
- ],
429
- "temperature": self.temperature,
430
- "max_tokens": 500
431
- }
432
-
433
- try:
434
- async with self.session.post(
435
- "https://api.openai.com/v1/chat/completions",
436
- headers=headers,
437
- json=payload,
438
- timeout=60
439
- ) as response:
440
- if response.status == 200:
441
- data = await response.json()
442
- return data["choices"][0]["message"]["content"].strip()
443
- else:
444
- error = await response.text()
445
- print(f"OpenAI API error: {error}")
446
- except Exception as e:
447
- print(f"OpenAI API call failed: {e}")
448
- return ""
449
-
450
- async def _call_anthropic(self, prompt: str, content: str) -> str:
451
- """Call Anthropic API"""
452
- api_key = os.environ.get("ANTHROPIC_API_KEY")
453
- if not api_key:
454
- raise ValueError("ANTHROPIC_API_KEY not set")
455
-
456
- headers = {
457
- "x-api-key": api_key,
458
- "Content-Type": "application/json",
459
- "anthropic-version": "2023-06-01"
460
- }
461
-
462
- payload = {
463
- "model": self.model if "claude" in self.model else "claude-3-sonnet-20240229",
464
- "max_tokens": 500,
465
- "messages": [
466
- {"role": "user", "content": f"{prompt}\n\nAnalyze this content:\n\n{content}"}
467
- ]
468
- }
469
-
470
- try:
471
- async with self.session.post(
472
- "https://api.anthropic.com/v1/messages",
473
- headers=headers,
474
- json=payload,
475
- timeout=60
476
- ) as response:
477
- if response.status == 200:
478
- data = await response.json()
479
- return data["content"][0]["text"].strip()
480
- except Exception as e:
481
- print(f"Anthropic API call failed: {e}")
482
- return ""
483
-
484
- def parse_score(self, response: str) -> int:
485
- """Extract numeric score from LLM response"""
486
- # Try to find a number 1-10
487
- import re
488
- numbers = re.findall(r'\b([1-9]|10)\b', response)
489
- if numbers:
490
- return int(numbers[0])
491
- return 5 # Default to middle score
492
-
493
- def calculate_overall_score(self, score: EEATScore) -> float:
494
- """Calculate weighted overall score"""
495
- total = (
496
- score.authorship_score * self.weights["authorship"] +
497
- score.citation_score * self.weights["citation"] +
498
- score.effort_score * self.weights["effort"] +
499
- score.originality_score * self.weights["originality"] +
500
- score.intent_score * self.weights["intent"] +
501
- score.subjective_score * self.weights["subjective"] +
502
- score.writing_score * self.weights["writing"]
503
- )
504
- return round(total, 2)
505
-
506
- def calculate_grade(self, overall_score: float) -> str:
507
- """Convert score to letter grade"""
508
- if overall_score >= 8.0:
509
- return "A"
510
- elif overall_score >= 6.5:
511
- return "B"
512
- elif overall_score >= 5.0:
513
- return "C"
514
- elif overall_score >= 3.5:
515
- return "D"
516
- else:
517
- return "F"
518
-
519
- async def analyze_url(self, url: str) -> EEATScore:
520
- """Analyze a single URL for E-E-A-T"""
521
- print(f"Analyzing: {url}")
522
-
523
- score = EEATScore(url=url, analyzed_at=datetime.now().isoformat())
524
-
525
- # Fetch content
526
- content = await self.fetch_page_content(url)
527
- if not content:
528
- print(f" Could not fetch content for {url}")
529
- return score
530
-
531
- # Analyze each criterion
532
- criteria = [
533
- ("authorship", "authorship_score", "authorship_reasoning"),
534
- ("citation", "citation_score", "citation_reasoning"),
535
- ("effort", "effort_score", "effort_reasoning"),
536
- ("originality", "originality_score", "originality_reasoning"),
537
- ("intent", "intent_score", "intent_reasoning"),
538
- ("subjective", "subjective_score", "subjective_reasoning"),
539
- ("writing", "writing_score", "writing_reasoning"),
540
- ]
541
-
542
- for criterion, score_attr, reasoning_attr in criteria:
543
- print(f" Evaluating {criterion}...")
544
-
545
- # Get reasoning
546
- reasoning_prompt = PROMPTS[f"{criterion}_reasoning"]
547
- reasoning = await self.call_llm(reasoning_prompt, content)
548
- setattr(score, reasoning_attr, reasoning)
549
-
550
- # Get score
551
- score_prompt = PROMPTS[f"{criterion}_score"]
552
- score_response = await self.call_llm(score_prompt, content)
553
- numeric_score = self.parse_score(score_response)
554
- setattr(score, score_attr, numeric_score)
555
-
556
- print(f" Score: {numeric_score}/10")
557
-
558
- # Small delay to avoid rate limits
559
- await asyncio.sleep(0.5)
560
-
561
- # Calculate overall score and grade
562
- score.overall_score = self.calculate_overall_score(score)
563
- score.grade = self.calculate_grade(score.overall_score)
564
-
565
- print(f" Overall: {score.overall_score}/10 (Grade: {score.grade})")
566
-
567
- return score
568
-
569
- async def analyze_urls(self, urls: List[str]):
570
- """Analyze multiple URLs"""
571
- headers = {
572
- 'User-Agent': 'AIDevOps-EEATAnalyzer/1.0'
573
- }
574
-
575
- connector = aiohttp.TCPConnector(limit=5)
576
- timeout = aiohttp.ClientTimeout(total=120)
577
-
578
- async with aiohttp.ClientSession(
579
- headers=headers,
580
- connector=connector,
581
- timeout=timeout
582
- ) as session:
583
- self.session = session
584
-
585
- for url in urls:
586
- score = await self.analyze_url(url)
587
- self.scores.append(score)
588
-
589
- return self.scores
590
-
591
- def export_csv(self, filename: str):
592
- """Export scores to CSV"""
593
- filepath = self.output_dir / filename
594
-
595
- fieldnames = [
596
- 'url', 'overall_score', 'grade',
597
- 'authorship_score', 'authorship_reasoning',
598
- 'citation_score', 'citation_reasoning',
599
- 'effort_score', 'effort_reasoning',
600
- 'originality_score', 'originality_reasoning',
601
- 'intent_score', 'intent_reasoning',
602
- 'subjective_score', 'subjective_reasoning',
603
- 'writing_score', 'writing_reasoning',
604
- 'analyzed_at'
605
- ]
606
-
607
- with open(filepath, 'w', newline='', encoding='utf-8') as f:
608
- writer = csv.DictWriter(f, fieldnames=fieldnames)
609
- writer.writeheader()
610
- for score in self.scores:
611
- writer.writerow(asdict(score))
612
-
613
- print(f"Exported: {filepath}")
614
-
615
- def export_xlsx(self, filename: str):
616
- """Export scores to Excel with formatting"""
617
- if not HAS_OPENPYXL:
618
- print("openpyxl not installed, skipping XLSX export")
619
- return
620
-
621
- filepath = self.output_dir / filename
622
- wb = openpyxl.Workbook()
623
- ws = wb.active
624
- ws.title = "E-E-A-T Scores"
625
-
626
- # Headers
627
- headers = [
628
- 'URL', 'Overall Score', 'Grade',
629
- 'Authorship', 'Authorship Notes',
630
- 'Citation', 'Citation Notes',
631
- 'Effort', 'Effort Notes',
632
- 'Originality', 'Originality Notes',
633
- 'Intent', 'Intent Notes',
634
- 'Subjective', 'Subjective Notes',
635
- 'Writing', 'Writing Notes',
636
- 'Analyzed At'
637
- ]
638
-
639
- # Header styling
640
- header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
641
- header_font = Font(color="FFFFFF", bold=True)
642
-
643
- for col, header in enumerate(headers, 1):
644
- cell = ws.cell(row=1, column=col, value=header)
645
- cell.fill = header_fill
646
- cell.font = header_font
647
- cell.alignment = Alignment(horizontal='center')
648
-
649
- # Grade colors
650
- grade_colors = {
651
- 'A': '00B050', # Green
652
- 'B': '92D050', # Light green
653
- 'C': 'FFEB9C', # Yellow
654
- 'D': 'FFC7CE', # Light red
655
- 'F': 'FF0000', # Red
656
- }
657
-
658
- # Data rows
659
- for row_num, score in enumerate(self.scores, 2):
660
- ws.cell(row=row_num, column=1, value=score.url)
661
- ws.cell(row=row_num, column=2, value=score.overall_score)
662
-
663
- grade_cell = ws.cell(row=row_num, column=3, value=score.grade)
664
- if score.grade in grade_colors:
665
- grade_cell.fill = PatternFill(
666
- start_color=grade_colors[score.grade],
667
- end_color=grade_colors[score.grade],
668
- fill_type="solid"
669
- )
670
-
671
- ws.cell(row=row_num, column=4, value=score.authorship_score)
672
- ws.cell(row=row_num, column=5, value=score.authorship_reasoning)
673
- ws.cell(row=row_num, column=6, value=score.citation_score)
674
- ws.cell(row=row_num, column=7, value=score.citation_reasoning)
675
- ws.cell(row=row_num, column=8, value=score.effort_score)
676
- ws.cell(row=row_num, column=9, value=score.effort_reasoning)
677
- ws.cell(row=row_num, column=10, value=score.originality_score)
678
- ws.cell(row=row_num, column=11, value=score.originality_reasoning)
679
- ws.cell(row=row_num, column=12, value=score.intent_score)
680
- ws.cell(row=row_num, column=13, value=score.intent_reasoning)
681
- ws.cell(row=row_num, column=14, value=score.subjective_score)
682
- ws.cell(row=row_num, column=15, value=score.subjective_reasoning)
683
- ws.cell(row=row_num, column=16, value=score.writing_score)
684
- ws.cell(row=row_num, column=17, value=score.writing_reasoning)
685
- ws.cell(row=row_num, column=18, value=score.analyzed_at)
686
-
687
- # Adjust column widths
688
- ws.column_dimensions['A'].width = 50
689
- ws.column_dimensions['B'].width = 12
690
- ws.column_dimensions['C'].width = 8
691
- for col in ['D', 'F', 'H', 'J', 'L', 'N', 'P']:
692
- ws.column_dimensions[col].width = 10
693
- for col in ['E', 'G', 'I', 'K', 'M', 'O', 'Q']:
694
- ws.column_dimensions[col].width = 40
695
- ws.column_dimensions['R'].width = 20
696
-
697
- # Freeze header row
698
- ws.freeze_panes = 'A2'
699
-
700
- wb.save(filepath)
701
- print(f"Exported: {filepath}")
702
-
703
- def export_summary(self, filename: str = "eeat-summary.json"):
704
- """Export summary statistics"""
705
- if not self.scores:
706
- return
707
-
708
- summary = {
709
- "analyzed_at": datetime.now().isoformat(),
710
- "total_pages": len(self.scores),
711
- "average_scores": {
712
- "overall": round(sum(s.overall_score for s in self.scores) / len(self.scores), 2),
713
- "authorship": round(sum(s.authorship_score for s in self.scores) / len(self.scores), 2),
714
- "citation": round(sum(s.citation_score for s in self.scores) / len(self.scores), 2),
715
- "effort": round(sum(s.effort_score for s in self.scores) / len(self.scores), 2),
716
- "originality": round(sum(s.originality_score for s in self.scores) / len(self.scores), 2),
717
- "intent": round(sum(s.intent_score for s in self.scores) / len(self.scores), 2),
718
- "subjective": round(sum(s.subjective_score for s in self.scores) / len(self.scores), 2),
719
- "writing": round(sum(s.writing_score for s in self.scores) / len(self.scores), 2),
720
- },
721
- "grade_distribution": {
722
- "A": sum(1 for s in self.scores if s.grade == "A"),
723
- "B": sum(1 for s in self.scores if s.grade == "B"),
724
- "C": sum(1 for s in self.scores if s.grade == "C"),
725
- "D": sum(1 for s in self.scores if s.grade == "D"),
726
- "F": sum(1 for s in self.scores if s.grade == "F"),
727
- },
728
- "weakest_areas": [],
729
- "strongest_areas": []
730
- }
731
-
732
- # Find weakest and strongest areas
733
- avg_scores = summary["average_scores"]
734
- sorted_areas = sorted(
735
- [(k, v) for k, v in avg_scores.items() if k != "overall"],
736
- key=lambda x: x[1]
737
- )
738
- summary["weakest_areas"] = [a[0] for a in sorted_areas[:2]]
739
- summary["strongest_areas"] = [a[0] for a in sorted_areas[-2:]]
740
-
741
- filepath = self.output_dir / filename
742
- with open(filepath, 'w') as f:
743
- json.dump(summary, f, indent=2)
744
-
745
- print(f"Exported: {filepath}")
746
- return summary
747
-
748
-
749
- async def main():
750
- import argparse
751
-
752
- parser = argparse.ArgumentParser(description='E-E-A-T Score Analyzer')
753
- parser.add_argument('urls', nargs='+', help='URLs to analyze')
754
- parser.add_argument('--output', '-o', required=True, help='Output directory')
755
- parser.add_argument('--provider', default='openai', choices=['openai', 'anthropic'])
756
- parser.add_argument('--model', default='gpt-4o', help='LLM model to use')
757
- parser.add_argument('--format', '-f', choices=['csv', 'xlsx', 'all'], default='xlsx')
758
- parser.add_argument('--domain', help='Domain name for output files')
759
-
760
- args = parser.parse_args()
761
-
762
- # Create output directory
763
- output_dir = Path(args.output)
764
- output_dir.mkdir(parents=True, exist_ok=True)
765
-
766
- analyzer = EEATAnalyzer(
767
- output_dir=str(output_dir),
768
- provider=args.provider,
769
- model=args.model
770
- )
771
-
772
- await analyzer.analyze_urls(args.urls)
773
-
774
- # Generate filename
775
- domain = args.domain or "eeat-analysis"
776
- timestamp = datetime.now().strftime("%Y-%m-%d")
777
-
778
- if args.format in ("xlsx", "all"):
779
- analyzer.export_xlsx(f"{domain}-eeat-score-{timestamp}.xlsx")
780
- if args.format in ("csv", "all"):
781
- analyzer.export_csv(f"{domain}-eeat-score-{timestamp}.csv")
782
-
783
- summary = analyzer.export_summary()
784
-
785
- print(f"\n=== E-E-A-T Analysis Summary ===")
786
- print(f"Pages analyzed: {summary['total_pages']}")
787
- print(f"Average overall score: {summary['average_scores']['overall']}/10")
788
- print(f"Grade distribution: A={summary['grade_distribution']['A']}, "
789
- f"B={summary['grade_distribution']['B']}, C={summary['grade_distribution']['C']}, "
790
- f"D={summary['grade_distribution']['D']}, F={summary['grade_distribution']['F']}")
791
- print(f"Weakest areas: {', '.join(summary['weakest_areas'])}")
792
- print(f"Strongest areas: {', '.join(summary['strongest_areas'])}")
793
- print(f"\nResults saved to: {output_dir}")
794
-
795
-
796
- if __name__ == "__main__":
797
- asyncio.run(main())
798
- PYTHON_SCRIPT
799
- }
800
-
801
- # Analyze crawled pages
802
- do_analyze() {
803
- local input_file="$1"
804
- shift
805
-
806
- if [[ ! -f "$input_file" ]]; then
807
- print_error "Input file not found: $input_file"
808
- return 1
809
- fi
810
-
811
- # Parse options
812
- local output_base="$DEFAULT_OUTPUT_DIR"
813
- local format="$OUTPUT_FORMAT"
814
- local provider="$LLM_PROVIDER"
815
- local model="$LLM_MODEL"
816
-
817
- while [[ $# -gt 0 ]]; do
818
- case "$1" in
819
- --output)
820
- output_base="$2"
821
- shift 2
822
- ;;
823
- --format)
824
- format="$2"
825
- shift 2
826
- ;;
827
- --provider)
828
- provider="$2"
829
- shift 2
830
- ;;
831
- --model)
832
- model="$2"
833
- shift 2
834
- ;;
835
- *)
836
- shift
837
- ;;
838
- esac
839
- done
840
-
841
- # Extract URLs from crawl data
842
- local urls=()
843
- if [[ "$input_file" == *.json ]]; then
844
- # JSON format - extract URLs with status 200
845
- mapfile -t urls < <(jq -r '.[] | select(.status_code == 200) | .url' "$input_file" 2>/dev/null || echo "")
846
- elif [[ "$input_file" == *.csv ]]; then
847
- # CSV format - extract URLs from first column where status is 200
848
- mapfile -t urls < <(tail -n +2 "$input_file" | awk -F',' '$2 == "200" || $2 == 200 {gsub(/"/, "", $1); print $1}')
849
- fi
850
-
851
- if [[ ${#urls[@]} -eq 0 ]]; then
852
- print_error "No valid URLs found in input file"
853
- return 1
854
- fi
855
-
856
- print_header "E-E-A-T Score Analysis"
857
- print_info "Input: $input_file"
858
- print_info "URLs to analyze: ${#urls[@]}"
859
-
860
- # Determine domain from first URL
861
- local domain
862
- domain=$(get_domain "${urls[0]}")
863
-
864
- # Get output directory (use same as crawl data if in domain folder)
865
- local input_dir
866
- input_dir=$(dirname "$input_file")
867
- local output_dir
868
-
869
- if [[ "$input_dir" == *"$domain"* ]]; then
870
- output_dir="$input_dir"
871
- else
872
- output_dir=$(create_output_dir "$domain" "$output_base")
873
- fi
874
-
875
- print_info "Output: $output_dir"
876
-
877
- # Check Python dependencies
878
- if ! python3 -c "import aiohttp, bs4" 2>/dev/null; then
879
- print_warning "Installing Python dependencies..."
880
- pip3 install aiohttp beautifulsoup4 openpyxl --quiet
881
- fi
882
-
883
- # Generate and run analyzer
884
- local analyzer_script="/tmp/eeat_analyzer_$$.py"
885
- generate_analyzer_script > "$analyzer_script"
886
-
887
- # Limit to reasonable number for API costs
888
- local max_urls=50
889
- if [[ ${#urls[@]} -gt $max_urls ]]; then
890
- print_warning "Limiting analysis to first $max_urls URLs (of ${#urls[@]})"
891
- urls=("${urls[@]:0:$max_urls}")
892
- fi
893
-
894
- python3 "$analyzer_script" "${urls[@]}" \
895
- --output "$output_dir" \
896
- --provider "$provider" \
897
- --model "$model" \
898
- --format "$format" \
899
- --domain "$domain"
900
-
901
- rm -f "$analyzer_script"
902
-
903
- print_success "E-E-A-T analysis complete!"
904
- print_info "Results: $output_dir"
905
-
906
- return 0
907
- }
908
-
909
- # Score single URL
910
- do_score() {
911
- local url="$1"
912
- shift
913
-
914
- local verbose=false
915
- local output_base="$DEFAULT_OUTPUT_DIR"
916
-
917
- while [[ $# -gt 0 ]]; do
918
- case "$1" in
919
- --verbose|-v)
920
- verbose=true
921
- shift
922
- ;;
923
- --output)
924
- output_base="$2"
925
- shift 2
926
- ;;
927
- *)
928
- shift
929
- ;;
930
- esac
931
- done
932
-
933
- local domain
934
- domain=$(get_domain "$url")
935
- local output_dir
936
- output_dir=$(create_output_dir "$domain" "$output_base")
937
-
938
- print_header "E-E-A-T Score Analysis"
939
- print_info "URL: $url"
940
-
941
- # Check Python dependencies
942
- if ! python3 -c "import aiohttp, bs4" 2>/dev/null; then
943
- print_warning "Installing Python dependencies..."
944
- pip3 install aiohttp beautifulsoup4 openpyxl --quiet
945
- fi
946
-
947
- local analyzer_script="/tmp/eeat_analyzer_$$.py"
948
- generate_analyzer_script > "$analyzer_script"
949
-
950
- python3 "$analyzer_script" "$url" \
951
- --output "$output_dir" \
952
- --provider "$LLM_PROVIDER" \
953
- --model "$LLM_MODEL" \
954
- --format "all" \
955
- --domain "$domain"
956
-
957
- rm -f "$analyzer_script"
958
-
959
- print_success "Analysis complete!"
960
- print_info "Results: $output_dir"
961
-
962
- return 0
963
- }
964
-
965
- # Batch analyze URLs from file
966
- do_batch() {
967
- local urls_file="$1"
968
- shift
969
-
970
- if [[ ! -f "$urls_file" ]]; then
971
- print_error "URLs file not found: $urls_file"
972
- return 1
973
- fi
974
-
975
- local urls=()
976
- while IFS= read -r url; do
977
- [[ -n "$url" && ! "$url" =~ ^# ]] && urls+=("$url")
978
- done < "$urls_file"
979
-
980
- if [[ ${#urls[@]} -eq 0 ]]; then
981
- print_error "No URLs found in file"
982
- return 1
983
- fi
984
-
985
- local output_base="$DEFAULT_OUTPUT_DIR"
986
- local format="$OUTPUT_FORMAT"
987
-
988
- while [[ $# -gt 0 ]]; do
989
- case "$1" in
990
- --output)
991
- output_base="$2"
992
- shift 2
993
- ;;
994
- --format)
995
- format="$2"
996
- shift 2
997
- ;;
998
- *)
999
- shift
1000
- ;;
1001
- esac
1002
- done
1003
-
1004
- local domain
1005
- domain=$(get_domain "${urls[0]}")
1006
- local output_dir
1007
- output_dir=$(create_output_dir "$domain" "$output_base")
1008
-
1009
- print_header "E-E-A-T Batch Analysis"
1010
- print_info "URLs: ${#urls[@]}"
1011
- print_info "Output: $output_dir"
1012
-
1013
- # Check Python dependencies
1014
- if ! python3 -c "import aiohttp, bs4" 2>/dev/null; then
1015
- print_warning "Installing Python dependencies..."
1016
- pip3 install aiohttp beautifulsoup4 openpyxl --quiet
1017
- fi
1018
-
1019
- local analyzer_script="/tmp/eeat_analyzer_$$.py"
1020
- generate_analyzer_script > "$analyzer_script"
1021
-
1022
- python3 "$analyzer_script" "${urls[@]}" \
1023
- --output "$output_dir" \
1024
- --provider "$LLM_PROVIDER" \
1025
- --model "$LLM_MODEL" \
1026
- --format "$format" \
1027
- --domain "$domain"
1028
-
1029
- rm -f "$analyzer_script"
1030
-
1031
- print_success "Batch analysis complete!"
1032
- print_info "Results: $output_dir"
1033
-
1034
- return 0
1035
- }
1036
-
1037
- # Generate report from existing scores
1038
- do_report() {
1039
- local scores_file="$1"
1040
- shift
1041
-
1042
- if [[ ! -f "$scores_file" ]]; then
1043
- print_error "Scores file not found: $scores_file"
1044
- return 1
1045
- fi
1046
-
1047
- print_header "Generating E-E-A-T Report"
1048
- print_info "Input: $scores_file"
1049
-
1050
- # For now, just display summary from JSON
1051
- if [[ "$scores_file" == *.json ]]; then
1052
- if command -v jq &> /dev/null; then
1053
- jq '.' "$scores_file"
1054
- else
1055
- cat "$scores_file"
1056
- fi
1057
- fi
1058
-
1059
- return 0
1060
- }
1061
-
1062
- # Check status
1063
- check_status() {
1064
- print_header "E-E-A-T Score Helper Status"
1065
-
1066
- # Check dependencies
1067
- print_info "Checking dependencies..."
1068
-
1069
- if command -v curl &> /dev/null; then
1070
- print_success "curl: installed"
1071
- else
1072
- print_error "curl: not installed"
1073
- fi
1074
-
1075
- if command -v jq &> /dev/null; then
1076
- print_success "jq: installed"
1077
- else
1078
- print_error "jq: not installed"
1079
- fi
1080
-
1081
- if command -v python3 &> /dev/null; then
1082
- print_success "python3: installed"
1083
-
1084
- if python3 -c "import aiohttp" 2>/dev/null; then
1085
- print_success " aiohttp: installed"
1086
- else
1087
- print_warning " aiohttp: not installed (pip3 install aiohttp)"
1088
- fi
1089
-
1090
- if python3 -c "import bs4" 2>/dev/null; then
1091
- print_success " beautifulsoup4: installed"
1092
- else
1093
- print_warning " beautifulsoup4: not installed"
1094
- fi
1095
-
1096
- if python3 -c "import openpyxl" 2>/dev/null; then
1097
- print_success " openpyxl: installed"
1098
- else
1099
- print_warning " openpyxl: not installed"
1100
- fi
1101
- else
1102
- print_error "python3: not installed"
1103
- fi
1104
-
1105
- # Check API keys
1106
- print_info "Checking API keys..."
1107
-
1108
- if [[ -n "${OPENAI_API_KEY:-}" ]]; then
1109
- print_success "OPENAI_API_KEY: set"
1110
- else
1111
- print_warning "OPENAI_API_KEY: not set"
1112
- fi
1113
-
1114
- if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
1115
- print_success "ANTHROPIC_API_KEY: set"
1116
- else
1117
- print_info "ANTHROPIC_API_KEY: not set (optional)"
1118
- fi
1119
-
1120
- # Check config
1121
- if [[ -f "$CONFIG_FILE" ]]; then
1122
- print_success "Config: $CONFIG_FILE"
1123
- else
1124
- print_info "Config: using defaults"
1125
- fi
1126
-
1127
- return 0
1128
- }
1129
-
1130
- # Show help
1131
- show_help() {
1132
- cat << 'EOF'
1133
- E-E-A-T Score Helper - Content Quality Analysis
1134
-
1135
- Usage: eeat-score-helper.sh [command] [input] [options]
1136
-
1137
- Commands:
1138
- analyze <crawl-data> Analyze pages from site-crawler output
1139
- score <url> Score a single URL
1140
- batch <urls-file> Batch analyze URLs from a file
1141
- report <scores-file> Generate report from existing scores
1142
- status Check dependencies and configuration
1143
- help Show this help message
1144
-
1145
- Options:
1146
- --output <dir> Output directory (default: ~/Downloads)
1147
- --format <fmt> Output format: csv, xlsx, all (default: xlsx)
1148
- --provider <name> LLM provider: openai, anthropic (default: openai)
1149
- --model <name> LLM model (default: gpt-4o)
1150
- --verbose Show detailed output
1151
-
1152
- Examples:
1153
- # Analyze crawled pages
1154
- eeat-score-helper.sh analyze ~/Downloads/example.com/_latest/crawl-data.json
1155
-
1156
- # Score single URL
1157
- eeat-score-helper.sh score https://example.com/blog/article
1158
-
1159
- # Batch analyze
1160
- eeat-score-helper.sh batch urls.txt --format xlsx
1161
-
1162
- # Check status
1163
- eeat-score-helper.sh status
1164
-
1165
- Output Structure:
1166
- ~/Downloads/{domain}/{timestamp}/
1167
- - {domain}-eeat-score-{date}.xlsx E-E-A-T scores with reasoning
1168
- - {domain}-eeat-score-{date}.csv Same data in CSV format
1169
- - eeat-summary.json Summary statistics
1170
-
1171
- ~/Downloads/{domain}/_latest -> symlink to latest analysis
1172
-
1173
- Scoring Criteria (1-10 scale):
1174
- - Authorship & Expertise (15%): Author credentials, verifiable entity
1175
- - Citation Quality (15%): Source quality, substantiation
1176
- - Content Effort (15%): Replicability, depth, original research
1177
- - Original Content (15%): Unique perspective, new information
1178
- - Page Intent (15%): Helpful-first vs search-first
1179
- - Subjective Quality (15%): Engagement, clarity, credibility
1180
- - Writing Quality (10%): Lexical diversity, readability
1181
-
1182
- Grades:
1183
- A (8.0-10.0): Excellent E-E-A-T
1184
- B (6.5-7.9): Good E-E-A-T
1185
- C (5.0-6.4): Average E-E-A-T
1186
- D (3.5-4.9): Poor E-E-A-T
1187
- F (1.0-3.4): Very poor E-E-A-T
1188
-
1189
- Environment Variables:
1190
- OPENAI_API_KEY Required for OpenAI provider
1191
- ANTHROPIC_API_KEY Required for Anthropic provider
1192
-
1193
- Related:
1194
- - Site crawler: site-crawler-helper.sh
1195
- - Crawl4AI: crawl4ai-helper.sh
1196
- EOF
1197
- return 0
1198
- }
1199
-
1200
- # Main function
1201
- main() {
1202
- load_config
1203
-
1204
- local command="${1:-help}"
1205
- shift || true
1206
-
1207
- case "$command" in
1208
- analyze)
1209
- check_dependencies || exit 1
1210
- check_api_key || exit 1
1211
- do_analyze "$@"
1212
- ;;
1213
- score)
1214
- check_dependencies || exit 1
1215
- check_api_key || exit 1
1216
- do_score "$@"
1217
- ;;
1218
- batch)
1219
- check_dependencies || exit 1
1220
- check_api_key || exit 1
1221
- do_batch "$@"
1222
- ;;
1223
- report)
1224
- do_report "$@"
1225
- ;;
1226
- status)
1227
- check_status
1228
- ;;
1229
- help|-h|--help|"")
1230
- show_help
1231
- ;;
1232
- *)
1233
- print_error "Unknown command: $command"
1234
- show_help
1235
- exit 1
1236
- ;;
1237
- esac
1238
-
1239
- return 0
1240
- }
1241
-
1242
- main "$@"