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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +3 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +26 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +420 -1
- package/config/gate-templates.json +372 -0
- package/config/mcp-allowlists.json +25 -0
- package/config/model-candidates.json +59 -2
- package/config/model-tiers.json +4 -1
- package/package.json +79 -22
- package/public/compare.html +6 -0
- package/public/index.html +144 -11
- package/public/numbers.html +11 -11
- package/public/pro.html +22 -24
- package/scripts/agent-design-governance.js +211 -0
- package/scripts/agent-reasoning-traces.js +683 -0
- package/scripts/agent-reward-model.js +438 -0
- package/scripts/agent-stack-survival-audit.js +231 -0
- package/scripts/ai-engineering-stack-guardrails.js +256 -0
- package/scripts/billing.js +16 -4
- package/scripts/chatgpt-ads-readiness-pack.js +195 -0
- package/scripts/cli-schema.js +277 -0
- package/scripts/code-graph-guardrails.js +176 -0
- package/scripts/deepseek-v4-runtime-guardrails.js +253 -0
- package/scripts/gemini-embedding-policy.js +198 -0
- package/scripts/inference-cache-policy.js +39 -0
- package/scripts/judge-reward-function.js +396 -0
- package/scripts/llm-behavior-monitor.js +251 -0
- package/scripts/long-running-agent-context-guardrails.js +176 -0
- package/scripts/multimodal-retrieval-plan.js +31 -11
- package/scripts/oss-pr-opportunity-scout.js +240 -0
- package/scripts/proactive-agent-eval-guardrails.js +230 -0
- package/scripts/profile-router.js +5 -4
- package/scripts/prompting-operating-system.js +273 -0
- package/scripts/proxy-pointer-rag-guardrails.js +189 -0
- package/scripts/rag-precision-guardrails.js +202 -0
- package/scripts/rate-limiter.js +1 -1
- package/scripts/reasoning-efficiency-guardrails.js +176 -0
- package/scripts/reward-hacking-guardrails.js +251 -0
- package/scripts/seo-gsd.js +1201 -11
- package/scripts/single-use-credential-gate.js +182 -0
- package/scripts/structured-prompt-driven.js +226 -0
- package/scripts/telemetry-analytics.js +31 -6
- package/scripts/tool-registry.js +92 -0
- package/scripts/upstream-contribution-engine.js +379 -0
- package/scripts/vector-store.js +119 -4
- package/src/api/server.js +333 -100
- package/scripts/agents-sdk-sandbox-plan.js +0 -57
- package/scripts/ai-org-governance.js +0 -98
- package/scripts/artifact-agent-plan.js +0 -81
- package/scripts/enterprise-agent-rollout.js +0 -34
- package/scripts/experience-replay-governance.js +0 -69
- package/scripts/inference-economics.js +0 -53
- package/scripts/knowledge-layer-plan.js +0 -108
- package/scripts/memory-store-governance.js +0 -60
- package/scripts/post-training-governance.js +0 -34
- package/scripts/production-agent-readiness.js +0 -40
- package/scripts/scaling-law-claims.js +0 -60
- 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
|
+
};
|
package/scripts/billing.js
CHANGED
|
@@ -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)
|
|
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
|
|
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
|
-
//
|
|
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: '
|
|
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
|
+
}
|