thumbgate 1.16.12 → 1.16.19

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 (62) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +3 -1
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +26 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/bin/cli.js +420 -1
  9. package/config/gate-templates.json +372 -0
  10. package/config/mcp-allowlists.json +25 -0
  11. package/config/model-candidates.json +59 -2
  12. package/config/model-tiers.json +4 -1
  13. package/package.json +79 -22
  14. package/public/compare.html +6 -0
  15. package/public/index.html +144 -11
  16. package/public/numbers.html +11 -11
  17. package/public/pro.html +22 -24
  18. package/scripts/agent-design-governance.js +211 -0
  19. package/scripts/agent-reasoning-traces.js +683 -0
  20. package/scripts/agent-reward-model.js +438 -0
  21. package/scripts/agent-stack-survival-audit.js +231 -0
  22. package/scripts/ai-engineering-stack-guardrails.js +256 -0
  23. package/scripts/billing.js +16 -4
  24. package/scripts/chatgpt-ads-readiness-pack.js +195 -0
  25. package/scripts/cli-schema.js +277 -0
  26. package/scripts/code-graph-guardrails.js +176 -0
  27. package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
  28. package/scripts/gemini-embedding-policy.js +198 -0
  29. package/scripts/inference-cache-policy.js +39 -0
  30. package/scripts/judge-reward-function.js +396 -0
  31. package/scripts/llm-behavior-monitor.js +251 -0
  32. package/scripts/long-running-agent-context-guardrails.js +176 -0
  33. package/scripts/multimodal-retrieval-plan.js +31 -11
  34. package/scripts/oss-pr-opportunity-scout.js +240 -0
  35. package/scripts/proactive-agent-eval-guardrails.js +230 -0
  36. package/scripts/profile-router.js +5 -4
  37. package/scripts/prompting-operating-system.js +273 -0
  38. package/scripts/proxy-pointer-rag-guardrails.js +189 -0
  39. package/scripts/rag-precision-guardrails.js +202 -0
  40. package/scripts/rate-limiter.js +1 -1
  41. package/scripts/reasoning-efficiency-guardrails.js +176 -0
  42. package/scripts/reward-hacking-guardrails.js +251 -0
  43. package/scripts/seo-gsd.js +1201 -11
  44. package/scripts/single-use-credential-gate.js +182 -0
  45. package/scripts/structured-prompt-driven.js +226 -0
  46. package/scripts/telemetry-analytics.js +31 -6
  47. package/scripts/tool-registry.js +92 -0
  48. package/scripts/upstream-contribution-engine.js +379 -0
  49. package/scripts/vector-store.js +119 -4
  50. package/src/api/server.js +333 -100
  51. package/scripts/agents-sdk-sandbox-plan.js +0 -57
  52. package/scripts/ai-org-governance.js +0 -98
  53. package/scripts/artifact-agent-plan.js +0 -81
  54. package/scripts/enterprise-agent-rollout.js +0 -34
  55. package/scripts/experience-replay-governance.js +0 -69
  56. package/scripts/inference-economics.js +0 -53
  57. package/scripts/knowledge-layer-plan.js +0 -108
  58. package/scripts/memory-store-governance.js +0 -60
  59. package/scripts/post-training-governance.js +0 -34
  60. package/scripts/production-agent-readiness.js +0 -40
  61. package/scripts/scaling-law-claims.js +0 -60
  62. package/scripts/student-consistent-training.js +0 -73
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { listGateTemplates } = require('./gate-templates');
5
+
6
+ const AI_STACK_CATEGORY = 'AI Engineering Stack Safety';
7
+ const AI_STACK_TEMPLATE_IDS = new Set([
8
+ 'require-ai-gateway-control-plane',
9
+ 'require-progressive-mcp-tool-discovery',
10
+ 'require-agent-context-freshness',
11
+ 'require-risk-tiered-ai-review',
12
+ 'require-sandboxed-background-agent-runtime',
13
+ ]);
14
+
15
+ function splitCsv(value) {
16
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
17
+ if (value === undefined || value === null || value === true) return [];
18
+ return String(value)
19
+ .split(',')
20
+ .map((item) => item.trim())
21
+ .filter(Boolean);
22
+ }
23
+
24
+ function normalizeBoolean(value) {
25
+ if (value === true) return true;
26
+ if (value === false || value === undefined || value === null) return false;
27
+ return /^(1|true|yes|on)$/i.test(String(value).trim());
28
+ }
29
+
30
+ function toNumber(value) {
31
+ if (value === undefined || value === null || value === '') return null;
32
+ const number = Number(value);
33
+ return Number.isFinite(number) ? number : null;
34
+ }
35
+
36
+ function normalizeOptions(options = {}) {
37
+ const mcpToolCount = toNumber(options['mcp-tool-count'] || options.tools || options['tool-count']);
38
+ const gateway = normalizeBoolean(options.gateway || options['ai-gateway'] || options.proxy || options['proxy-worker']);
39
+ const directProviderKeys = normalizeBoolean(options['direct-provider-keys'] || options['keys-on-laptops']);
40
+ const codeMode = normalizeBoolean(options['code-mode'] || options['progressive-discovery']);
41
+ const agentsMd = normalizeBoolean(options['agents-md'] || options['agent-context']);
42
+ const llmWikiPages = toNumber(options['llm-wiki-pages'] || options['wiki-pages']);
43
+ const contextFreshnessDays = toNumber(options['context-freshness-days'] || options['stale-days']);
44
+ const aiReviewer = normalizeBoolean(options['ai-reviewer'] || options.reviewer);
45
+ const codexRules = normalizeBoolean(options['codex-rules'] || options['standards-as-skills'] || options.skills);
46
+ const backgroundAgents = normalizeBoolean(options['background-agents'] || options['durable-agents']);
47
+ const sandbox = normalizeBoolean(options.sandbox || options['sandbox-sdk'] || options['isolated-runtime']);
48
+ const workflows = splitCsv(options.workflows || options.workflow);
49
+ const highRiskWorkflows = splitCsv(options['high-risk-workflows'] || options['risky-workflows']);
50
+
51
+ return {
52
+ stackName: String(options.stack || options['stack-name'] || 'internal-ai-engineering-stack').trim() || 'internal-ai-engineering-stack',
53
+ gateway,
54
+ directProviderKeys,
55
+ mcpToolCount,
56
+ codeMode,
57
+ agentsMd,
58
+ llmWikiPages,
59
+ contextFreshnessDays,
60
+ aiReviewer,
61
+ codexRules,
62
+ backgroundAgents,
63
+ sandbox,
64
+ workflows,
65
+ highRiskWorkflows,
66
+ };
67
+ }
68
+
69
+ function templateApplicability(template, options) {
70
+ if (template.id === 'require-ai-gateway-control-plane') {
71
+ return options.directProviderKeys || !options.gateway;
72
+ }
73
+ if (template.id === 'require-progressive-mcp-tool-discovery') {
74
+ return (options.mcpToolCount !== null && options.mcpToolCount >= 20) || !options.codeMode;
75
+ }
76
+ if (template.id === 'require-agent-context-freshness') {
77
+ return !options.agentsMd ||
78
+ (options.llmWikiPages !== null && options.llmWikiPages > 0) ||
79
+ (options.contextFreshnessDays !== null && options.contextFreshnessDays > 14);
80
+ }
81
+ if (template.id === 'require-risk-tiered-ai-review') {
82
+ return !options.aiReviewer || !options.codexRules || options.highRiskWorkflows.length > 0;
83
+ }
84
+ if (template.id === 'require-sandboxed-background-agent-runtime') {
85
+ return options.backgroundAgents && !options.sandbox;
86
+ }
87
+ return false;
88
+ }
89
+
90
+ function buildSignals(options) {
91
+ return [
92
+ gatewaySignal(options),
93
+ mcpContextSignal(options),
94
+ contextFreshnessSignal(options),
95
+ reviewEnforcementSignal(options),
96
+ backgroundRuntimeSignal(options),
97
+ ].filter(Boolean);
98
+ }
99
+
100
+ function gatewaySignal(options) {
101
+ if (options.gateway && !options.directProviderKeys) return null;
102
+ return {
103
+ id: 'gateway_control_plane',
104
+ label: 'Model gateway control plane',
105
+ values: [
106
+ options.gateway ? 'gateway present' : 'gateway missing',
107
+ options.directProviderKeys ? 'direct provider keys detected' : null,
108
+ ].filter(Boolean),
109
+ risk: 'AI usage, keys, cost attribution, and data-retention controls fragment across clients',
110
+ };
111
+ }
112
+
113
+ function mcpContextSignal(options) {
114
+ if (!((options.mcpToolCount !== null && options.mcpToolCount >= 20) || !options.codeMode)) return null;
115
+ return {
116
+ id: 'mcp_context_bloat',
117
+ label: 'MCP tool schema overhead',
118
+ values: [
119
+ options.mcpToolCount !== null ? `${options.mcpToolCount} MCP tools` : null,
120
+ options.codeMode ? 'progressive discovery enabled' : 'code mode missing',
121
+ ].filter(Boolean),
122
+ risk: 'large tool schemas consume prompt budget before the agent starts work',
123
+ };
124
+ }
125
+
126
+ function contextFreshnessSignal(options) {
127
+ if (options.agentsMd && options.llmWikiPages === null && options.contextFreshnessDays === null) return null;
128
+ return {
129
+ id: 'agent_context_freshness',
130
+ label: 'AGENTS.md and LLM wiki freshness',
131
+ values: [
132
+ options.agentsMd ? 'AGENTS.md present' : 'AGENTS.md missing',
133
+ options.llmWikiPages !== null ? `${options.llmWikiPages} LLM wiki pages` : null,
134
+ options.contextFreshnessDays !== null ? `${options.contextFreshnessDays} days since refresh` : null,
135
+ ].filter(Boolean),
136
+ risk: 'agents act on stale repo conventions, ownership, tests, or system dependencies',
137
+ };
138
+ }
139
+
140
+ function reviewEnforcementSignal(options) {
141
+ if (options.aiReviewer && options.codexRules && options.highRiskWorkflows.length === 0) return null;
142
+ return {
143
+ id: 'review_enforcement_gap',
144
+ label: 'Risk-tiered AI review and standards',
145
+ values: [
146
+ options.aiReviewer ? 'AI reviewer present' : 'AI reviewer missing',
147
+ options.codexRules ? 'standards-as-skills present' : 'codex rules missing',
148
+ ...options.highRiskWorkflows,
149
+ ].filter(Boolean),
150
+ risk: 'agent changes ship without repeatable severity, category, and rule-id feedback',
151
+ };
152
+ }
153
+
154
+ function backgroundRuntimeSignal(options) {
155
+ if (!options.backgroundAgents && options.sandbox) return null;
156
+ return {
157
+ id: 'background_agent_runtime',
158
+ label: 'Background agent runtime isolation',
159
+ values: [
160
+ options.backgroundAgents ? 'background agents enabled' : 'background agents not declared',
161
+ options.sandbox ? 'sandbox present' : 'sandbox missing',
162
+ ].filter(Boolean),
163
+ risk: 'long-running agents can clone, build, test, or publish without durable audit and isolation',
164
+ };
165
+ }
166
+
167
+ function buildAiEngineeringStackGuardrailsPlan(rawOptions = {}, templatesPath) {
168
+ const options = normalizeOptions(rawOptions);
169
+ const templates = listGateTemplates(templatesPath)
170
+ .filter((template) => template.category === AI_STACK_CATEGORY && AI_STACK_TEMPLATE_IDS.has(template.id))
171
+ .map((template) => ({
172
+ ...template,
173
+ recommended: templateApplicability(template, options),
174
+ }));
175
+ const signals = buildSignals(options);
176
+ const recommendedTemplates = templates.filter((template) => template.recommended);
177
+
178
+ return {
179
+ name: 'thumbgate-ai-engineering-stack-guardrails',
180
+ status: recommendedTemplates.length > 0 ? 'actionable' : 'ready',
181
+ stackName: options.stackName,
182
+ posture: {
183
+ gateway: options.gateway,
184
+ directProviderKeys: options.directProviderKeys,
185
+ mcpToolCount: options.mcpToolCount,
186
+ codeMode: options.codeMode,
187
+ agentsMd: options.agentsMd,
188
+ llmWikiPages: options.llmWikiPages,
189
+ contextFreshnessDays: options.contextFreshnessDays,
190
+ aiReviewer: options.aiReviewer,
191
+ codexRules: options.codexRules,
192
+ backgroundAgents: options.backgroundAgents,
193
+ sandbox: options.sandbox,
194
+ workflows: options.workflows,
195
+ highRiskWorkflows: options.highRiskWorkflows,
196
+ },
197
+ summary: {
198
+ signalCount: signals.length,
199
+ templateCount: templates.length,
200
+ recommendedTemplateCount: recommendedTemplates.length,
201
+ },
202
+ signals,
203
+ templates,
204
+ nextActions: [
205
+ 'Put every model request behind one gateway/proxy so keys, cost, provider routing, and retention policy are centralized.',
206
+ 'Collapse large MCP surfaces behind progressive discovery or code-mode search/execute tools before schema bloat burns context.',
207
+ 'Generate short AGENTS.md and LLM wiki pages from source metadata, then gate stale context when repo structure changes.',
208
+ 'Require risk-tiered AI review that cites standards-as-skills before high-risk agent changes merge.',
209
+ 'Run background agents in isolated, durable environments with build/test logs and resumable session state.',
210
+ ],
211
+ exampleCommand: 'npx thumbgate ai-engineering-stack-guardrails --mcp-tool-count=182 --direct-provider-keys --llm-wiki-pages=24 --context-freshness-days=30 --background-agents --high-risk-workflows=deploy,billing --json',
212
+ };
213
+ }
214
+
215
+ function formatAiEngineeringStackGuardrailsPlan(report) {
216
+ const lines = [
217
+ '',
218
+ 'ThumbGate AI Engineering Stack Guardrails',
219
+ '-'.repeat(43),
220
+ `Status : ${report.status}`,
221
+ `Stack : ${report.stackName}`,
222
+ `Signals : ${report.summary.signalCount}`,
223
+ `Templates: ${report.summary.recommendedTemplateCount}/${report.summary.templateCount} recommended`,
224
+ ];
225
+
226
+ if (report.signals.length > 0) {
227
+ lines.push('', 'Detected stack risk signals:');
228
+ for (const signal of report.signals) {
229
+ lines.push(` - ${signal.label}: ${signal.values.join(', ') || 'needs evidence'}`);
230
+ lines.push(` Risk: ${signal.risk}`);
231
+ }
232
+ }
233
+
234
+ lines.push('', 'Recommended templates:');
235
+ const recommended = report.templates.filter((template) => template.recommended);
236
+ if (recommended.length === 0) {
237
+ lines.push(' - No AI-stack gaps were passed. Keep monitoring gateway, MCP, context, review, and sandbox evidence.');
238
+ } else {
239
+ for (const template of recommended) {
240
+ lines.push(` - ${template.id} [${template.defaultAction}]`);
241
+ lines.push(` ${template.roi}`);
242
+ }
243
+ }
244
+
245
+ lines.push('', 'Next actions:');
246
+ for (const action of report.nextActions) lines.push(` - ${action}`);
247
+ lines.push('', `Example: ${report.exampleCommand}`, '');
248
+ return `${lines.join('\n')}\n`;
249
+ }
250
+
251
+ module.exports = {
252
+ AI_STACK_CATEGORY,
253
+ buildAiEngineeringStackGuardrailsPlan,
254
+ formatAiEngineeringStackGuardrailsPlan,
255
+ normalizeOptions,
256
+ };
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const STRIPE_TIMEOUT_MS = 5000;
9
+ const STRIPE_RECONCILIATION_SUMMARY_TIMEOUT_MS = 3500;
9
10
  function withTimeout(promise, ms = STRIPE_TIMEOUT_MS) {
10
11
  return Promise.race([
11
12
  promise,
@@ -1127,7 +1128,13 @@ function deriveRevenueEventFromPaidProviderEvent(entry = {}) {
1127
1128
 
1128
1129
  function loadResolvedRevenueEvents(options = {}) {
1129
1130
  const analyticsWindow = resolveAnalyticsWindow(options);
1130
- const extraRevenueEvents = Array.isArray(options.extraRevenueEvents) ? options.extraRevenueEvents : [];
1131
+ const extraRevenueEvents = Array.isArray(options.extraRevenueEvents)
1132
+ ? filterEntriesForWindow(
1133
+ options.extraRevenueEvents,
1134
+ analyticsWindow,
1135
+ (entry) => entry && entry.timestamp
1136
+ )
1137
+ : [];
1131
1138
  const revenueEvents = filterEntriesForWindow(
1132
1139
  loadRevenueLedger(),
1133
1140
  analyticsWindow,
@@ -2371,7 +2378,12 @@ function getBillingSummary(options = {}) {
2371
2378
 
2372
2379
  async function getBillingSummaryLive(options = {}) {
2373
2380
  try {
2374
- const extraRevenueEvents = await listStripeReconciledRevenueEvents().catch(() => []);
2381
+ const reconciliationTimeoutMs = normalizeInteger(options.stripeReconciliationTimeoutMs)
2382
+ || STRIPE_RECONCILIATION_SUMMARY_TIMEOUT_MS;
2383
+ const extraRevenueEvents = await withTimeout(
2384
+ listStripeReconciledRevenueEvents(),
2385
+ reconciliationTimeoutMs
2386
+ ).catch(() => []);
2375
2387
  return getBillingSummary({
2376
2388
  ...options,
2377
2389
  extraRevenueEvents,
@@ -2527,10 +2539,10 @@ function buildCheckoutSessionPayload({ successUrl, cancelUrl, customerEmail, che
2527
2539
  packId: pack ? pack.id : null,
2528
2540
  credits: pack ? pack.credits : null,
2529
2541
  }),
2530
- // 7-day free trial for subscriptions don't require card upfront
2542
+ // Keep the trial, but require a payment method so trials can convert without dunning.
2531
2543
  ...(pack ? {} : {
2532
2544
  subscription_data: { trial_period_days: 7 },
2533
- payment_method_collection: 'if_required',
2545
+ payment_method_collection: 'always',
2534
2546
  }),
2535
2547
  };
2536
2548
 
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const SOURCE = Object.freeze({
8
+ searchEngineLandUrl: 'https://searchengineland.com/advertisers-test-chatgpt-ads-manager-475114',
9
+ openAiAdsHelpUrl: 'https://help.openai.com/articles/20001047-ads-in-chatgpt',
10
+ openAiAdvertisersUrl: 'https://openai.com/advertisers',
11
+ title: 'Advertisers test ChatGPT Ads Manager',
12
+ observedAt: '2026-04-22',
13
+ });
14
+
15
+ function normalizeText(value) {
16
+ if (value === undefined || value === null) return '';
17
+ return String(value).trim();
18
+ }
19
+
20
+ function splitList(value) {
21
+ if (Array.isArray(value)) return value.map(String).map((item) => item.trim()).filter(Boolean);
22
+ return String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
23
+ }
24
+
25
+ function parseNumber(value, fallback = 0) {
26
+ const parsed = Number(value);
27
+ return Number.isFinite(parsed) ? parsed : fallback;
28
+ }
29
+
30
+ function normalizeOptions(raw = {}) {
31
+ return {
32
+ offer: normalizeText(raw.offer) || 'ThumbGate Pro and Workflow Hardening Sprint',
33
+ audience: normalizeText(raw.audience) || 'AI coding teams and developers comparing agent governance tools',
34
+ budget: parseNumber(raw.budget || raw['test-budget'], 500),
35
+ keywords: splitList(raw.keywords || raw.queries).length
36
+ ? splitList(raw.keywords || raw.queries)
37
+ : [
38
+ 'AI coding agent keeps repeating mistakes',
39
+ 'Claude Code pre tool use hook',
40
+ 'Cursor agent guardrails',
41
+ 'AI agent verification before PR',
42
+ 'agent governance for coding teams',
43
+ ],
44
+ proofLinks: splitList(raw.proofLinks || raw['proof-links']).length
45
+ ? splitList(raw.proofLinks || raw['proof-links'])
46
+ : [
47
+ 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md',
48
+ 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md',
49
+ ],
50
+ };
51
+ }
52
+
53
+ function buildAdGroups(options) {
54
+ return [
55
+ {
56
+ id: 'agent-governance-intent',
57
+ theme: 'developers asking how to make AI coding agents safer',
58
+ keywords: options.keywords.filter((keyword) => /agent|guardrail|governance|verification/i.test(keyword)).slice(0, 8),
59
+ landingPage: 'https://thumbgate.ai/guide?utm_source=chatgpt_ads&utm_medium=paid_ai&utm_campaign=agent_governance_intent',
60
+ primaryCta: 'Install the proof-backed guide',
61
+ },
62
+ {
63
+ id: 'workflow-hardening-intent',
64
+ theme: 'teams describing repeated autonomous-agent workflow failures',
65
+ keywords: [
66
+ 'AI agent made same mistake again',
67
+ 'coding agent opened bad PR',
68
+ 'agent changed production without approval',
69
+ 'AI workflow hardening sprint',
70
+ ],
71
+ landingPage: 'https://thumbgate.ai/?utm_source=chatgpt_ads&utm_medium=paid_ai&utm_campaign=workflow_hardening_intent#workflow-sprint-intake',
72
+ primaryCta: 'Book one workflow hardening sprint',
73
+ },
74
+ ];
75
+ }
76
+
77
+ function buildCreative(options) {
78
+ return [
79
+ {
80
+ id: 'proof-before-pr',
81
+ headline: 'Stop AI agents before repeat mistakes become PRs.',
82
+ body: 'ThumbGate turns feedback into pre-action gates for Claude Code, Cursor, Codex, Gemini CLI, and MCP agents. Require proof before risky tool calls or completion claims.',
83
+ proofRequired: options.proofLinks,
84
+ },
85
+ {
86
+ id: 'workflow-sprint',
87
+ headline: 'One repeated agent failure. One hardened workflow.',
88
+ body: 'Use ThumbGate to capture the mistake, retrieve the lesson, enforce the gate, and show verification evidence before the next autonomous change.',
89
+ proofRequired: options.proofLinks,
90
+ },
91
+ ];
92
+ }
93
+
94
+ function buildMeasurementPlan(options) {
95
+ return {
96
+ budget: options.budget,
97
+ primaryConversion: 'workflow_sprint_intake_or_pro_checkout_start',
98
+ guardrailMetrics: [
99
+ { id: 'landing_claim_proof_rate', target: '1.00' },
100
+ { id: 'cost_per_qualified_intake', target: '<= budget / 2' },
101
+ { id: 'proof_link_click_rate', target: '>= 0.05' },
102
+ { id: 'unsupported_ad_claims', target: '0' },
103
+ ],
104
+ attributionParams: {
105
+ utm_source: 'chatgpt_ads',
106
+ utm_medium: 'paid_ai',
107
+ utm_campaign: 'agent_governance_or_workflow_hardening',
108
+ },
109
+ };
110
+ }
111
+
112
+ function buildChatgptAdsReadinessPack(rawOptions = {}) {
113
+ const options = normalizeOptions(rawOptions);
114
+ const adGroups = buildAdGroups(options);
115
+ return {
116
+ name: 'thumbgate-chatgpt-ads-readiness-pack',
117
+ source: SOURCE,
118
+ status: 'ready_for_interest_signup',
119
+ offer: options.offer,
120
+ audience: options.audience,
121
+ strategy: {
122
+ channelThesis: 'If ChatGPT Ads Manager becomes self-serve, high-intent conversational queries can capture developers at the moment they are asking how to trust or govern AI agents.',
123
+ trustBoundary: 'OpenAI states ads are separate from answers and clearly labeled, so the campaign must win with proof-backed landing pages rather than implying answer influence.',
124
+ firstMove: 'Submit advertiser interest, prepare exact-match intent clusters, and route traffic to guide or workflow sprint pages with proof links.',
125
+ },
126
+ adGroups,
127
+ creative: buildCreative(options),
128
+ measurement: buildMeasurementPlan(options),
129
+ launchChecklist: [
130
+ 'Submit interest at openai.com/advertisers.',
131
+ 'Create a ChatGPT ads UTM namespace before first spend.',
132
+ 'Use guide landing page for self-serve developer intent.',
133
+ 'Use workflow sprint intake for high-risk team workflow pain.',
134
+ 'Block unsupported claims in ad copy and landing pages.',
135
+ 'Compare paid AI traffic against organic AI-search visibility before scaling budget.',
136
+ ],
137
+ marketingAngle: {
138
+ headline: 'ChatGPT ads are a paid surface for the exact moment developers ask how to trust agents.',
139
+ subhead: 'ThumbGate should be ready with proof-backed copy, intent clusters, and conversion routes before self-serve inventory gets crowded.',
140
+ replyDraft: 'If ChatGPT Ads Manager becomes self-serve, ThumbGate should test it early but stay evidence-first: bid on agent-governance pain, route to the setup guide or workflow sprint, and never imply ads influence ChatGPT answers. The wedge is proof-backed trust at the moment someone asks how to make agents safer.',
141
+ },
142
+ };
143
+ }
144
+
145
+ function formatChatgptAdsReadinessPack(report) {
146
+ const lines = [
147
+ '',
148
+ 'ThumbGate ChatGPT Ads Readiness Pack',
149
+ '-'.repeat(38),
150
+ `Status : ${report.status}`,
151
+ `Offer : ${report.offer}`,
152
+ `Audience: ${report.audience}`,
153
+ `Source : ${report.source.searchEngineLandUrl}`,
154
+ '',
155
+ 'Ad groups:',
156
+ ];
157
+ for (const group of report.adGroups) {
158
+ lines.push(` - ${group.id}: ${group.theme}`);
159
+ lines.push(` Landing: ${group.landingPage}`);
160
+ }
161
+ lines.push('', 'Launch checklist:');
162
+ for (const item of report.launchChecklist) lines.push(` - ${item}`);
163
+ lines.push('', `Reply draft: ${report.marketingAngle.replyDraft}`, '');
164
+ return `${lines.join('\n')}\n`;
165
+ }
166
+
167
+ function writeChatgptAdsReadinessPack(outputDir = path.join(__dirname, '..', 'docs', 'marketing'), options = {}) {
168
+ const report = buildChatgptAdsReadinessPack(options);
169
+ fs.mkdirSync(outputDir, { recursive: true });
170
+ const jsonPath = path.join(outputDir, 'chatgpt-ads-readiness-pack.json');
171
+ const markdownPath = path.join(outputDir, 'chatgpt-ads-readiness-pack.md');
172
+ fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
173
+ fs.writeFileSync(markdownPath, formatChatgptAdsReadinessPack(report));
174
+ return { report, jsonPath, markdownPath };
175
+ }
176
+
177
+ module.exports = {
178
+ SOURCE,
179
+ buildAdGroups,
180
+ buildChatgptAdsReadinessPack,
181
+ buildCreative,
182
+ buildMeasurementPlan,
183
+ formatChatgptAdsReadinessPack,
184
+ normalizeOptions,
185
+ writeChatgptAdsReadinessPack,
186
+ };
187
+
188
+ function isCliInvocation(argv = process.argv) {
189
+ return Boolean(argv[1] && path.resolve(argv[1]) === __filename);
190
+ }
191
+
192
+ if (isCliInvocation()) {
193
+ const { jsonPath, markdownPath } = writeChatgptAdsReadinessPack();
194
+ console.log(JSON.stringify({ jsonPath, markdownPath }, null, 2));
195
+ }