2020 CONTENT_FACTORY_ARTICLE_COST_POINTS ,
2121 CONTENT_FACTORY_REQUEST_SOURCE ,
2222 build_live_status_blocks ,
23+ get_content_factory_article_cost_points ,
24+ is_free_content_factory_domain ,
25+ normalize_content_factory_domain ,
2326)
2427from ..content_intent import is_explicit_scan_request
2528from ..llm import chat , embed , get_llm_client
@@ -221,6 +224,13 @@ def _build_article_direction_blocks(
221224 client_request_id : str ,
222225 ) -> list [dict ]:
223226 """Build the preflight prompt for generic article requests."""
227+ normalized_domain = normalize_content_factory_domain (domain ) or domain
228+ cost_points = get_content_factory_article_cost_points (domain )
229+ cost_text = (
230+ f"*Cost:* Articles for *{ normalized_domain } * are free. No Roo points will be deducted for this run."
231+ if cost_points == 0
232+ else f"*Cost:* Starting the article run deducts { cost_points } Roo points."
233+ )
224234 button_value = json .dumps (
225235 {
226236 "domain" : domain ,
@@ -241,7 +251,7 @@ def _build_article_direction_blocks(
241251 "If you do, send it through and I'll still research the best keywords, title, "
242252 "and talking points so the article has the best chance to rank.\n \n "
243253 "If you don't, I can research the strongest article opportunity for you.\n \n "
244- f"*Cost:* Starting the article run deducts { CONTENT_FACTORY_ARTICLE_COST_POINTS } Roo points. "
254+ f"{ cost_text } "
245255 ),
246256 },
247257 },
@@ -1129,15 +1139,23 @@ async def _validate_content_factory_paid_access(
11291139 self ,
11301140 api_client : Any ,
11311141 user_id : str ,
1142+ domain : Optional [str ],
11321143 ) -> Optional [str ]:
11331144 from ..slack_client import get_user_info
11341145
1146+ normalized_domain = normalize_content_factory_domain (domain ) or "this domain"
1147+ cost_points = get_content_factory_article_cost_points (domain )
11351148 slack_info = get_user_info (user_id )
11361149 email = str (slack_info .get ("email" ) or "" ).strip ().lower ()
11371150 if not email :
1151+ if cost_points == 0 :
1152+ return (
1153+ "I need access to your real Slack email before I can start Content Factory for you. "
1154+ f"Once that's available, articles for { normalized_domain } are free."
1155+ )
11381156 return (
11391157 "I need access to your real Slack email before I can start Content Factory for you. "
1140- f"Once that's available, creating an article costs { CONTENT_FACTORY_ARTICLE_COST_POINTS } Roo points."
1158+ f"Once that's available, creating an article costs { cost_points } Roo points."
11411159 )
11421160
11431161 real_name = str (slack_info .get ("real_name" ) or "" ).strip ()
@@ -1161,6 +1179,9 @@ async def _validate_content_factory_paid_access(
11611179 "Please try again in a moment."
11621180 )
11631181
1182+ if cost_points == 0 :
1183+ return None
1184+
11641185 try :
11651186 balance_data = await api_client .get_balance (user_id )
11661187 except Exception as exc :
@@ -1171,9 +1192,9 @@ async def _validate_content_factory_paid_access(
11711192 )
11721193
11731194 balance = int (balance_data .get ("balance" ) or 0 )
1174- if balance < CONTENT_FACTORY_ARTICLE_COST_POINTS :
1195+ if balance < cost_points :
11751196 return (
1176- f"Creating an article costs { CONTENT_FACTORY_ARTICLE_COST_POINTS } Roo points, and you currently have "
1197+ f"Creating an article costs { cost_points } Roo points, and you currently have "
11771198 f"{ balance } . Earn a few more Roo points first, then ask me again."
11781199 )
11791200
@@ -1292,6 +1313,16 @@ async def _execute_content_factory(
12921313 "thread_ts" : thread_ts ,
12931314 }
12941315 )
1316+ disclaimer_cost_text = (
1317+ f"*Cost:* Articles for **{ normalize_content_factory_domain (domain ) or domain or 'this domain' } ** are free. "
1318+ "No Roo points will be deducted for this run.\n \n "
1319+ if is_free_content_factory_domain (domain )
1320+ else (
1321+ f"*Cost:* Creating an article costs **{ CONTENT_FACTORY_ARTICLE_COST_POINTS } Roo points**. "
1322+ "Those points are deducted when article research/generation starts. "
1323+ "If something goes wrong, message Dr Sam on Slack and he can help sort out a refund.\n \n "
1324+ )
1325+ )
12951326
12961327 blocks = [
12971328 {
@@ -1309,9 +1340,7 @@ async def _execute_content_factory(
13091340 "text" : (
13101341 "Before we start, a quick heads-up! This skill works best with **Next.js & Tailwind CSS** projects "
13111342 "and requires a connected **GitHub repository**.\n \n "
1312- f"*Cost:* Creating an article costs **{ CONTENT_FACTORY_ARTICLE_COST_POINTS } Roo points**. "
1313- "Those points are deducted when article research/generation starts. "
1314- "If something goes wrong, message Dr Sam on Slack and he can help sort out a refund.\n \n "
1343+ f"{ disclaimer_cost_text } "
13151344 "*How it works:*\n "
13161345 "1. 🏗️ **Scans** your repo for blog structure (creates it if missing)\n "
13171346 "2. 🧩 **Checks** for reusable components (Hero, CTAs) or creates them\n "
@@ -1996,7 +2025,7 @@ async def _execute_content_factory(
19962025 history_str = "\n " .join ([f"{ msg .get ('user' )} : { msg .get ('text' )} " for msg in thread_history [:- 1 ]])
19972026 full_context = f"Context from Thread:\n { history_str } \n \n Current Request: { text } "
19982027
1999- access_error = await self ._validate_content_factory_paid_access (api_client , user_id )
2028+ access_error = await self ._validate_content_factory_paid_access (api_client , user_id , domain )
20002029 if access_error :
20012030 return access_error
20022031
0 commit comments