thumbgate 1.14.1 → 1.16.0

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 (150) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.well-known/llms.txt +5 -5
  4. package/.well-known/mcp/server-card.json +1 -1
  5. package/README.md +60 -35
  6. package/adapters/chatgpt/openapi.yaml +118 -2
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/mcp/server-stdio.js +217 -84
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bench/prompt-eval-suite.json +5 -1
  11. package/bin/cli.js +211 -8
  12. package/config/enforcement.json +59 -7
  13. package/config/evals/agent-safety-eval.json +338 -22
  14. package/config/gates/default.json +33 -0
  15. package/config/gates/routine.json +43 -0
  16. package/config/github-about.json +3 -3
  17. package/config/mcp-allowlists.json +4 -0
  18. package/config/merge-quality-checks.json +2 -1
  19. package/config/model-candidates.json +131 -0
  20. package/openapi/openapi.yaml +118 -2
  21. package/package.json +70 -51
  22. package/public/blog.html +7 -7
  23. package/public/codex-plugin.html +13 -7
  24. package/public/compare.html +29 -23
  25. package/public/dashboard.html +105 -12
  26. package/public/guide.html +28 -28
  27. package/public/index.html +233 -97
  28. package/public/learn.html +87 -20
  29. package/public/lessons.html +26 -2
  30. package/public/numbers.html +271 -0
  31. package/public/pro.html +89 -19
  32. package/scripts/agent-audit-trace.js +55 -0
  33. package/scripts/agent-memory-lifecycle.js +96 -0
  34. package/scripts/agent-readiness-plan.js +118 -0
  35. package/scripts/agentic-data-pipeline.js +21 -1
  36. package/scripts/agents-sdk-sandbox-plan.js +57 -0
  37. package/scripts/ai-org-governance.js +98 -0
  38. package/scripts/ai-search-distribution.js +43 -0
  39. package/scripts/artifact-agent-plan.js +81 -0
  40. package/scripts/billing.js +27 -8
  41. package/scripts/cli-feedback.js +2 -1
  42. package/scripts/cli-schema.js +60 -5
  43. package/scripts/code-mode-mcp-plan.js +71 -0
  44. package/scripts/commercial-offer.js +1 -1
  45. package/scripts/context-engine.js +1 -2
  46. package/scripts/context-manager.js +4 -1
  47. package/scripts/contextfs.js +214 -32
  48. package/scripts/dashboard-render-spec.js +1 -1
  49. package/scripts/dashboard.js +275 -9
  50. package/scripts/decision-journal.js +13 -3
  51. package/scripts/document-workflow-governance.js +62 -0
  52. package/scripts/enterprise-agent-rollout.js +34 -0
  53. package/scripts/experience-replay-governance.js +69 -0
  54. package/scripts/export-hf-dataset.js +1 -1
  55. package/scripts/feedback-loop.js +141 -9
  56. package/scripts/feedback-to-rules.js +17 -23
  57. package/scripts/gates-engine.js +4 -6
  58. package/scripts/growth-campaigns.js +49 -0
  59. package/scripts/harness-selector.js +145 -1
  60. package/scripts/hybrid-supervisor-agent.js +64 -0
  61. package/scripts/inference-cache-policy.js +72 -0
  62. package/scripts/inference-economics.js +53 -0
  63. package/scripts/internal-agent-bootstrap.js +12 -2
  64. package/scripts/knowledge-layer-plan.js +108 -0
  65. package/scripts/lesson-canonical.js +181 -0
  66. package/scripts/lesson-db.js +71 -10
  67. package/scripts/lesson-inference.js +183 -44
  68. package/scripts/lesson-search.js +4 -1
  69. package/scripts/lesson-synthesis.js +23 -2
  70. package/scripts/llm-client.js +157 -26
  71. package/scripts/mailer/resend-mailer.js +112 -1
  72. package/scripts/mcp-transport-strategy.js +66 -0
  73. package/scripts/memory-store-governance.js +60 -0
  74. package/scripts/meta-agent-loop.js +7 -13
  75. package/scripts/model-access-eligibility.js +38 -0
  76. package/scripts/model-migration-readiness.js +55 -0
  77. package/scripts/native-messaging-audit.js +514 -0
  78. package/scripts/operational-integrity.js +96 -3
  79. package/scripts/otel-declarative-config.js +56 -0
  80. package/scripts/perplexity-client.js +1 -1
  81. package/scripts/post-training-governance.js +34 -0
  82. package/scripts/pr-manager.js +47 -7
  83. package/scripts/private-core-boundary.js +72 -0
  84. package/scripts/production-agent-readiness.js +40 -0
  85. package/scripts/profile-router.js +16 -1
  86. package/scripts/prompt-eval.js +564 -32
  87. package/scripts/prompt-programs.js +93 -0
  88. package/scripts/provider-action-normalizer.js +585 -0
  89. package/scripts/rule-validator.js +285 -0
  90. package/scripts/scaling-law-claims.js +60 -0
  91. package/scripts/security-scanner.js +1 -1
  92. package/scripts/self-distill-agent.js +7 -32
  93. package/scripts/seo-gsd.js +400 -43
  94. package/scripts/skill-rag-router.js +53 -0
  95. package/scripts/spec-gate.js +1 -1
  96. package/scripts/student-consistent-training.js +73 -0
  97. package/scripts/synthetic-data-provenance.js +98 -0
  98. package/scripts/task-context-result.js +81 -0
  99. package/scripts/telemetry-analytics.js +149 -0
  100. package/scripts/thompson-sampling.js +2 -2
  101. package/scripts/token-savings.js +7 -6
  102. package/scripts/token-tco.js +46 -0
  103. package/scripts/tool-registry.js +75 -3
  104. package/scripts/verification-loop.js +10 -1
  105. package/scripts/verifier-scoring.js +71 -0
  106. package/scripts/workflow-sentinel.js +284 -28
  107. package/scripts/workspace-agent-routines.js +118 -0
  108. package/skills/thumbgate/SKILL.md +1 -1
  109. package/src/api/server.js +434 -120
  110. package/.claude-plugin/README.md +0 -170
  111. package/adapters/README.md +0 -12
  112. package/scripts/analytics-report.js +0 -328
  113. package/scripts/autonomous-workflow.js +0 -377
  114. package/scripts/billing-setup.js +0 -109
  115. package/scripts/creator-campaigns.js +0 -239
  116. package/scripts/cross-encoder-reranker.js +0 -235
  117. package/scripts/daemon-manager.js +0 -108
  118. package/scripts/decision-trace.js +0 -354
  119. package/scripts/delegation-runtime.js +0 -896
  120. package/scripts/dispatch-brief.js +0 -159
  121. package/scripts/distribution-surfaces.js +0 -110
  122. package/scripts/feedback-history-distiller.js +0 -382
  123. package/scripts/funnel-analytics.js +0 -35
  124. package/scripts/history-distiller.js +0 -200
  125. package/scripts/hosted-job-launcher.js +0 -256
  126. package/scripts/intent-router.js +0 -392
  127. package/scripts/lesson-reranker.js +0 -263
  128. package/scripts/lesson-retrieval.js +0 -148
  129. package/scripts/managed-lesson-agent.js +0 -183
  130. package/scripts/operational-dashboard.js +0 -103
  131. package/scripts/operational-summary.js +0 -129
  132. package/scripts/operator-artifacts.js +0 -608
  133. package/scripts/optimize-context.js +0 -17
  134. package/scripts/org-dashboard.js +0 -206
  135. package/scripts/partner-orchestration.js +0 -146
  136. package/scripts/predictive-insights.js +0 -356
  137. package/scripts/pulse.js +0 -80
  138. package/scripts/reflector-agent.js +0 -221
  139. package/scripts/sales-pipeline.js +0 -681
  140. package/scripts/session-episode-store.js +0 -329
  141. package/scripts/session-health-sensor.js +0 -242
  142. package/scripts/session-report.js +0 -120
  143. package/scripts/swarm-coordinator.js +0 -81
  144. package/scripts/tool-kpi-tracker.js +0 -12
  145. package/scripts/webhook-delivery.js +0 -62
  146. package/scripts/workflow-sprint-intake.js +0 -475
  147. package/skills/agent-memory/SKILL.md +0 -97
  148. package/skills/solve-architecture-autonomy/SKILL.md +0 -17
  149. package/skills/solve-architecture-autonomy/tool.js +0 -33
  150. package/skills/thumbgate-feedback/SKILL.md +0 -49
@@ -1,377 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const { ensureDir } = require('./fs-utils');
8
- const {
9
- executeJob,
10
- readJobState,
11
- resumeJob,
12
- } = require('./async-job-runner');
13
- const {
14
- createCheckpoint,
15
- advanceCheckpoint,
16
- loadCheckpoint,
17
- saveCheckpoint,
18
- } = require('./workflow-gate-checkpoint');
19
- const { appendWorkflowRun } = require('./workflow-runs');
20
-
21
- function normalizeText(value) {
22
- if (value === undefined || value === null) return '';
23
- return String(value).trim();
24
- }
25
-
26
- function slugify(value, fallback = 'workflow') {
27
- // Avoid any `-+` quantifier in an edge-anchored regex (Sonar javascript:S5852
28
- // still flags even the anchored form). Strip edge dashes with a linear scan.
29
- const collapsed = normalizeText(value).toLowerCase().replace(/[^a-z0-9]+/g, '-');
30
- let start = 0;
31
- let end = collapsed.length;
32
- while (start < end && collapsed.charCodeAt(start) === 45) start += 1;
33
- while (end > start && collapsed.charCodeAt(end - 1) === 45) end -= 1;
34
- const normalized = collapsed.slice(start, end);
35
- return normalized || fallback;
36
- }
37
-
38
- function getWorkflowPaths(workflowId, cwd = process.cwd()) {
39
- const rootDir = path.join(cwd, '.thumbgate', 'autonomous-workflows', workflowId);
40
- return {
41
- rootDir,
42
- checkpointPath: path.join(rootDir, 'checkpoint.json'),
43
- reportJsonPath: path.join(rootDir, 'report.json'),
44
- reportMdPath: path.join(rootDir, 'report.md'),
45
- planPath: path.join(rootDir, 'plan.json'),
46
- };
47
- }
48
-
49
- function normalizePlan(input, workflowId) {
50
- if (Array.isArray(input)) {
51
- return {
52
- workflowId,
53
- summary: input.map((step) => normalizeText(step)).filter(Boolean).join(' | ') || 'Execution plan ready',
54
- steps: input
55
- .map((step, index) => ({
56
- id: `step_${index + 1}`,
57
- description: normalizeText(step),
58
- }))
59
- .filter((step) => step.description),
60
- };
61
- }
62
-
63
- if (input && typeof input === 'object') {
64
- const steps = Array.isArray(input.steps)
65
- ? input.steps
66
- .map((step, index) => {
67
- if (typeof step === 'string') {
68
- return {
69
- id: `step_${index + 1}`,
70
- description: normalizeText(step),
71
- };
72
- }
73
-
74
- if (step && typeof step === 'object') {
75
- return {
76
- id: normalizeText(step.id) || `step_${index + 1}`,
77
- description: normalizeText(step.description || step.summary || step.name),
78
- };
79
- }
80
-
81
- return null;
82
- })
83
- .filter(Boolean)
84
- : [];
85
-
86
- return {
87
- workflowId,
88
- summary: normalizeText(input.summary) || steps.map((step) => step.description).join(' | ') || 'Execution plan ready',
89
- steps,
90
- };
91
- }
92
-
93
- const summary = normalizeText(input) || 'Execution plan ready';
94
- return {
95
- workflowId,
96
- summary,
97
- steps: summary ? [{ id: 'step_1', description: summary }] : [],
98
- };
99
- }
100
-
101
- function buildDefaultPlan(spec, workflowId) {
102
- const executionSteps = Array.isArray(spec.stages)
103
- ? spec.stages.map((stage, index) => normalizeText(stage && (stage.name || stage.context || stage.command)) || `Stage ${index + 1}`)
104
- : [];
105
-
106
- return normalizePlan({
107
- summary: normalizeText(spec.planSummary) || `Run ${executionSteps.length || 0} execution stage(s) and verify output`,
108
- steps: [
109
- { id: 'intent', description: normalizeText(spec.intent) || 'Intent captured' },
110
- { id: 'plan', description: 'Execution plan generated' },
111
- ...executionSteps.map((description, index) => ({
112
- id: `execute_${index + 1}`,
113
- description,
114
- })),
115
- { id: 'verify', description: 'Verification loop completed' },
116
- { id: 'report', description: 'Evidence-backed report recorded' },
117
- ],
118
- workflowId,
119
- }, workflowId);
120
- }
121
-
122
- function resolvePlan(spec, workflowId) {
123
- if (typeof spec.plan === 'function') {
124
- return normalizePlan(spec.plan(spec), workflowId);
125
- }
126
-
127
- if (spec.plan) {
128
- return normalizePlan(spec.plan, workflowId);
129
- }
130
-
131
- return buildDefaultPlan(spec, workflowId);
132
- }
133
-
134
- function buildExecutionJob(spec, workflowId, paths, plan) {
135
- return {
136
- id: spec.jobId || `${workflowId}-execution`,
137
- tags: Array.isArray(spec.tags) ? spec.tags : [],
138
- skill: spec.skill || 'autonomous-workflow',
139
- partnerProfile: spec.partnerProfile || null,
140
- verificationMode: spec.verificationMode === 'none' ? 'none' : 'standard',
141
- autoImprove: spec.autoImprove !== false,
142
- recordFeedback: spec.recordFeedback !== false,
143
- stages: Array.isArray(spec.stages) ? spec.stages : [],
144
- metadata: {
145
- workflowId,
146
- planSummary: plan.summary,
147
- workflowRoot: paths.rootDir,
148
- },
149
- };
150
- }
151
-
152
- function writeWorkflowPlan(paths, plan) {
153
- ensureDir(paths.rootDir);
154
- fs.writeFileSync(paths.planPath, `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
155
- return paths.planPath;
156
- }
157
-
158
- function collectEvidenceArtifacts(paths, executionResult, extraArtifacts = []) {
159
- return [
160
- paths.checkpointPath,
161
- paths.planPath,
162
- paths.reportJsonPath,
163
- paths.reportMdPath,
164
- executionResult && executionResult.jobStatePath ? executionResult.jobStatePath : null,
165
- ...extraArtifacts,
166
- ].filter(Boolean);
167
- }
168
-
169
- function writeWorkflowReport(paths, report) {
170
- ensureDir(paths.rootDir);
171
- fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
172
-
173
- const markdown = [
174
- `# ${report.workflowName}`,
175
- '',
176
- `- Workflow ID: ${report.workflowId}`,
177
- `- Status: ${report.status}`,
178
- `- Intent: ${report.intent}`,
179
- `- Verification accepted: ${report.verification ? String(report.verification.accepted) : 'skipped'}`,
180
- `- Evidence artifacts: ${report.evidenceArtifacts.length}`,
181
- '',
182
- '## Plan',
183
- '',
184
- report.plan.summary,
185
- '',
186
- ...report.plan.steps.map((step) => `- ${step.id}: ${step.description}`),
187
- '',
188
- '## Execution',
189
- '',
190
- ...report.execution.stageHistory.map((stage) => `- ${stage.name} @ ${stage.completedAt}`),
191
- '',
192
- '## Evidence Artifacts',
193
- '',
194
- ...report.evidenceArtifacts.map((artifact) => `- ${artifact}`),
195
- ].join('\n');
196
-
197
- fs.writeFileSync(paths.reportMdPath, `${markdown}\n`, 'utf8');
198
- return {
199
- json: paths.reportJsonPath,
200
- markdown: paths.reportMdPath,
201
- };
202
- }
203
-
204
- function recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, feedbackDir) {
205
- const proofBacked = report.status === 'completed'
206
- && (!report.verification || report.verification.accepted)
207
- && evidenceArtifacts.length > 0;
208
-
209
- return appendWorkflowRun({
210
- workflowId: report.workflowId,
211
- workflowName: report.workflowName,
212
- owner: spec.owner || 'automation',
213
- runtime: 'node',
214
- status: report.status,
215
- customerType: spec.customerType || 'internal_dogfood',
216
- teamId: spec.teamId || null,
217
- reviewed: proofBacked,
218
- reviewedBy: proofBacked ? (spec.reviewedBy || 'automation') : null,
219
- proofBacked,
220
- proofArtifacts: evidenceArtifacts,
221
- source: spec.source || 'autonomous-workflow',
222
- metadata: {
223
- intent: report.intent,
224
- planSummary: report.plan.summary,
225
- verificationAttempts: report.verification ? report.verification.attempts : 0,
226
- executionJobId: report.execution.jobId,
227
- },
228
- }, feedbackDir);
229
- }
230
-
231
- function runAutonomousWorkflow(spec = {}, options = {}) {
232
- const cwd = options.cwd || process.cwd();
233
- const workflowId = normalizeText(spec.workflowId) || slugify(spec.name || spec.intent, 'autonomous-workflow');
234
- const workflowName = normalizeText(spec.name) || `Autonomous workflow ${workflowId}`;
235
- const intent = normalizeText(spec.intent) || 'Intent not provided';
236
- const paths = getWorkflowPaths(workflowId, cwd);
237
- const plan = resolvePlan(spec, workflowId);
238
-
239
- writeWorkflowPlan(paths, plan);
240
-
241
- let checkpoint = createCheckpoint({
242
- workflowId,
243
- phase: 'intent',
244
- status: 'running',
245
- intent: { summary: intent },
246
- plan,
247
- evidence: [paths.planPath],
248
- metadata: {
249
- workflowName,
250
- },
251
- });
252
- saveCheckpoint(checkpoint, paths.checkpointPath);
253
-
254
- checkpoint = advanceCheckpoint(checkpoint, {
255
- phase: 'plan',
256
- status: 'running',
257
- plan,
258
- evidence: [paths.planPath],
259
- });
260
- saveCheckpoint(checkpoint, paths.checkpointPath);
261
-
262
- const job = buildExecutionJob(spec, workflowId, paths, plan);
263
- const executionResult = options.resume === true
264
- ? resumeJob(job.id, job)
265
- : executeJob(job);
266
- const jobState = readJobState(job.id);
267
-
268
- checkpoint = advanceCheckpoint(checkpoint, {
269
- phase: 'verify',
270
- status: executionResult.status,
271
- evidence: jobState && jobState.verification ? [paths.checkpointPath] : [],
272
- metadata: {
273
- executionJobId: job.id,
274
- executionStatus: executionResult.status,
275
- },
276
- });
277
- saveCheckpoint(checkpoint, paths.checkpointPath);
278
-
279
- const report = {
280
- workflowId,
281
- workflowName,
282
- status: executionResult.status,
283
- intent,
284
- plan,
285
- execution: {
286
- jobId: job.id,
287
- status: executionResult.status,
288
- stageHistory: Array.isArray(jobState && jobState.stageHistory) ? jobState.stageHistory : [],
289
- checkpointCount: Array.isArray(jobState && jobState.checkpoints) ? jobState.checkpoints.length : 0,
290
- currentContext: jobState && jobState.currentContext ? jobState.currentContext : '',
291
- jobStatePath: jobState ? path.join(getFeedbackDir(options.feedbackDir), 'jobs', job.id, 'state.json') : null,
292
- },
293
- verification: executionResult.phases ? executionResult.phases.verification : null,
294
- phases: executionResult.phases || null,
295
- timestamp: new Date().toISOString(),
296
- evidenceArtifacts: [],
297
- };
298
-
299
- const evidenceArtifacts = collectEvidenceArtifacts(paths, report.execution, spec.proofArtifacts);
300
- report.evidenceArtifacts = evidenceArtifacts;
301
-
302
- checkpoint = advanceCheckpoint(checkpoint, {
303
- phase: 'report',
304
- status: executionResult.status,
305
- report: {
306
- status: report.status,
307
- generatedAt: report.timestamp,
308
- },
309
- evidence: evidenceArtifacts,
310
- });
311
- saveCheckpoint(checkpoint, paths.checkpointPath);
312
-
313
- writeWorkflowReport(paths, report);
314
- report.workflowRun = recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, options.feedbackDir);
315
- fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
316
-
317
- return report;
318
- }
319
-
320
- function getFeedbackDir(feedbackDir) {
321
- if (feedbackDir) return feedbackDir;
322
- return process.env.THUMBGATE_FEEDBACK_DIR || path.join(process.cwd(), '.thumbgate');
323
- }
324
-
325
- function resumeAutonomousWorkflow(spec = {}, options = {}) {
326
- return runAutonomousWorkflow(spec, { ...options, resume: true });
327
- }
328
-
329
- function readWorkflowReport(workflowId, options = {}) {
330
- const paths = getWorkflowPaths(workflowId, options.cwd || process.cwd());
331
- if (!fs.existsSync(paths.reportJsonPath)) return null;
332
- return JSON.parse(fs.readFileSync(paths.reportJsonPath, 'utf8'));
333
- }
334
-
335
- function isCliInvocation(argv = process.argv) {
336
- const invokedPath = argv[1];
337
- return invokedPath ? path.resolve(invokedPath) === __filename : false;
338
- }
339
-
340
- function parseArgs(argv = process.argv.slice(2)) {
341
- const args = {};
342
- for (const arg of argv) {
343
- if (!arg.startsWith('--')) continue;
344
- const [key, ...rest] = arg.slice(2).split('=');
345
- args[key] = rest.length > 0 ? rest.join('=') : true;
346
- }
347
- return args;
348
- }
349
-
350
- if (isCliInvocation()) {
351
- const args = parseArgs();
352
- if (!args.file) {
353
- console.error('Usage: node scripts/autonomous-workflow.js --file=workflow.json [--resume]');
354
- process.exit(1);
355
- }
356
-
357
- const specPath = path.resolve(args.file);
358
- const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
359
- const report = args.resume ? resumeAutonomousWorkflow(spec) : runAutonomousWorkflow(spec);
360
- console.log(JSON.stringify(report, null, 2));
361
- process.exit(report.status === 'completed' ? 0 : 1);
362
- }
363
-
364
- module.exports = {
365
- buildDefaultPlan,
366
- collectEvidenceArtifacts,
367
- getWorkflowPaths,
368
- normalizePlan,
369
- parseArgs,
370
- readWorkflowReport,
371
- recordAutonomousWorkflowRun,
372
- resumeAutonomousWorkflow,
373
- runAutonomousWorkflow,
374
- slugify,
375
- writeWorkflowPlan,
376
- writeWorkflowReport,
377
- };
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- /**
5
- * billing-setup.js — Wire up hosted billing for the CFO dashboard
6
- *
7
- * Generates a THUMBGATE_OPERATOR_KEY and stores it locally so that
8
- * `node bin/cli.js cfo --today` pulls live revenue from the production server.
9
- *
10
- * Usage:
11
- * node scripts/billing-setup.js
12
- *
13
- * After running, set the printed key on Railway:
14
- * railway variables set THUMBGATE_OPERATOR_KEY=<key>
15
- * Then redeploy (or let Railway auto-deploy).
16
- */
17
-
18
- const crypto = require('node:crypto');
19
- const fs = require('node:fs');
20
- const path = require('node:path');
21
- const os = require('node:os');
22
-
23
- const LOCAL_CONFIG_PATH = path.join(os.homedir(), '.config', 'thumbgate', 'operator.json');
24
- const PROD_URL = 'https://thumbgate-production.up.railway.app';
25
-
26
- function generateOperatorKey() {
27
- return `tg_op_${crypto.randomBytes(20).toString('hex')}`;
28
- }
29
-
30
- function loadExistingConfig() {
31
- try {
32
- const raw = fs.readFileSync(LOCAL_CONFIG_PATH, 'utf8');
33
- return JSON.parse(raw);
34
- } catch {
35
- return null;
36
- }
37
- }
38
-
39
- function saveConfig(config) {
40
- const dir = path.dirname(LOCAL_CONFIG_PATH);
41
- fs.mkdirSync(dir, { recursive: true });
42
- fs.writeFileSync(LOCAL_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
43
- }
44
-
45
- async function verifyEndpoint(baseUrl, key) {
46
- try {
47
- const url = new URL('/v1/billing/summary?window=today', baseUrl);
48
- const res = await fetch(url, {
49
- method: 'GET',
50
- headers: { authorization: `Bearer ${key}`, accept: 'application/json' },
51
- });
52
- return { status: res.status, ok: res.ok };
53
- } catch (err) {
54
- return { status: null, ok: false, error: err.message };
55
- }
56
- }
57
-
58
- async function main() {
59
- const existing = loadExistingConfig();
60
-
61
- if (existing && existing.operatorKey && existing.baseUrl) {
62
- console.log('\n✓ Operator config already exists at', LOCAL_CONFIG_PATH);
63
- console.log(' Base URL :', existing.baseUrl);
64
- console.log(' Operator key:', existing.operatorKey);
65
- console.log('\nTo regenerate, delete the file and re-run this script.');
66
-
67
- // Set env for this process so the verify check works
68
- process.env.THUMBGATE_OPERATOR_KEY = existing.operatorKey;
69
- process.env.THUMBGATE_BILLING_API_BASE_URL = existing.baseUrl;
70
-
71
- const check = await verifyEndpoint(existing.baseUrl, existing.operatorKey);
72
- if (check.ok) {
73
- console.log('\n✓ Production endpoint responds OK — hosted billing is active.');
74
- } else if (check.status === 403) {
75
- console.log('\n⚠ Production endpoint returned 403 — the operator key is not yet set on Railway.');
76
- console.log('\nSet it now:\n');
77
- console.log(` railway variables set THUMBGATE_OPERATOR_KEY=${existing.operatorKey}`);
78
- console.log('\nThen redeploy (Railway will pick it up automatically).');
79
- } else {
80
- console.log(`\n⚠ Endpoint check returned status ${check.status || 'error'}: ${check.error || ''}`);
81
- }
82
- return;
83
- }
84
-
85
- const key = generateOperatorKey();
86
- const config = {
87
- operatorKey: key,
88
- baseUrl: process.env.THUMBGATE_BILLING_API_BASE_URL || PROD_URL,
89
- createdAt: new Date().toISOString(),
90
- };
91
-
92
- saveConfig(config);
93
-
94
- console.log('\n✓ Operator key generated and saved to', LOCAL_CONFIG_PATH);
95
- console.log('\n──────────────────────────────────────────────────────');
96
- console.log(' THUMBGATE_OPERATOR_KEY =', key);
97
- console.log('──────────────────────────────────────────────────────');
98
- console.log('\nSet this key on Railway (one-time):');
99
- console.log('\n railway variables set THUMBGATE_OPERATOR_KEY=' + key);
100
- console.log('\nOr paste it into the Railway dashboard under Variables.');
101
- console.log('\nAfter Railway redeploys, run:\n');
102
- console.log(' node bin/cli.js cfo --today\n');
103
- console.log('The CFO dashboard will use live production billing data.');
104
- }
105
-
106
- main().catch((err) => {
107
- console.error('billing-setup error:', err.message);
108
- process.exit(1);
109
- });