thumbgate 1.27.18 → 1.27.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 (96) hide show
  1. package/.claude-plugin/marketplace.json +6 -6
  2. package/.claude-plugin/plugin.json +4 -3
  3. package/.well-known/agentic-verify.txt +1 -0
  4. package/.well-known/llms.txt +33 -12
  5. package/.well-known/mcp/server-card.json +8 -8
  6. package/README.md +249 -30
  7. package/adapters/chatgpt/openapi.yaml +12 -0
  8. package/adapters/claude/.mcp.json +2 -2
  9. package/adapters/codex/config.toml +2 -2
  10. package/adapters/gemini/function-declarations.json +1 -0
  11. package/adapters/mcp/server-stdio.js +263 -11
  12. package/adapters/opencode/opencode.json +1 -1
  13. package/bench/thumbgate-bench.json +2 -2
  14. package/bin/cli.js +1429 -121
  15. package/bin/postinstall.js +1 -8
  16. package/config/gate-classifier-routing.json +98 -0
  17. package/config/gate-templates.json +216 -0
  18. package/config/gates/claim-verification.json +12 -0
  19. package/config/gates/default.json +31 -2
  20. package/config/github-about.json +2 -2
  21. package/config/mcp-allowlists.json +23 -13
  22. package/config/merge-quality-checks.json +0 -1
  23. package/config/model-candidates.json +121 -6
  24. package/config/post-deploy-marketing-pages.json +80 -0
  25. package/config/tessl-tiles.json +1 -3
  26. package/openapi/openapi.yaml +12 -0
  27. package/package.json +1 -1
  28. package/public/blog.html +4 -4
  29. package/public/codex-plugin.html +72 -20
  30. package/public/compare.html +31 -8
  31. package/public/dashboard.html +930 -166
  32. package/public/federal.html +2 -2
  33. package/public/guide.html +33 -13
  34. package/public/index.html +469 -111
  35. package/public/learn.html +183 -18
  36. package/public/lessons.html +168 -10
  37. package/public/numbers.html +7 -7
  38. package/public/pro.html +34 -11
  39. package/scripts/agent-memory-lifecycle.js +211 -0
  40. package/scripts/agent-readiness.js +20 -3
  41. package/scripts/agent-reward-model.js +53 -1
  42. package/scripts/auto-promote-gates.js +82 -10
  43. package/scripts/auto-wire-hooks.js +14 -0
  44. package/scripts/billing.js +93 -1
  45. package/scripts/bot-detection.js +61 -3
  46. package/scripts/build-metadata.js +50 -10
  47. package/scripts/cli-feedback.js +4 -2
  48. package/scripts/cli-schema.js +97 -0
  49. package/scripts/cli-telemetry.js +6 -1
  50. package/scripts/commercial-offer.js +82 -2
  51. package/scripts/context-manager.js +74 -6
  52. package/scripts/dashboard.js +68 -2
  53. package/scripts/export-databricks-bundle.js +5 -1
  54. package/scripts/export-dpo-pairs.js +7 -2
  55. package/scripts/feedback-loop.js +123 -1
  56. package/scripts/feedback-quality.js +87 -0
  57. package/scripts/filesystem-search.js +35 -10
  58. package/scripts/gate-stats.js +89 -0
  59. package/scripts/gates-engine.js +1176 -85
  60. package/scripts/gemini-embedding-policy.js +2 -1
  61. package/scripts/hook-runtime.js +20 -14
  62. package/scripts/hook-thumbgate-cache-updater.js +18 -2
  63. package/scripts/hybrid-feedback-context.js +142 -7
  64. package/scripts/lesson-inference.js +8 -3
  65. package/scripts/lesson-search.js +17 -1
  66. package/scripts/license.js +10 -10
  67. package/scripts/llm-client.js +169 -4
  68. package/scripts/local-model-profile.js +15 -8
  69. package/scripts/mcp-config.js +7 -1
  70. package/scripts/memory-scope-readiness.js +159 -0
  71. package/scripts/meta-agent-loop.js +36 -0
  72. package/scripts/operational-integrity.js +39 -5
  73. package/scripts/oss-pr-opportunity-scout.js +35 -5
  74. package/scripts/plausible-server-events.js +9 -6
  75. package/scripts/pro-local-dashboard.js +4 -4
  76. package/scripts/proxy-pointer-rag-guardrails.js +42 -1
  77. package/scripts/published-cli.js +0 -8
  78. package/scripts/rate-limiter.js +64 -13
  79. package/scripts/secret-scanner.js +44 -5
  80. package/scripts/security-scanner.js +260 -10
  81. package/scripts/self-distill-agent.js +3 -1
  82. package/scripts/seo-gsd.js +916 -7
  83. package/scripts/statusline-cache-path.js +17 -2
  84. package/scripts/statusline-local-stats.js +9 -1
  85. package/scripts/statusline-meta.js +28 -2
  86. package/scripts/statusline.sh +20 -4
  87. package/scripts/telemetry-analytics.js +357 -0
  88. package/scripts/thompson-sampling.js +31 -10
  89. package/scripts/thumbgate-bench.js +16 -1
  90. package/scripts/thumbgate-search.js +85 -19
  91. package/scripts/tool-registry.js +169 -1
  92. package/scripts/vector-store.js +45 -0
  93. package/scripts/workflow-sentinel.js +286 -53
  94. package/scripts/workspace-evolver.js +62 -2
  95. package/src/api/server.js +2683 -319
  96. package/scripts/bot-detector.js +0 -50
@@ -6,42 +6,78 @@ const path = require('path');
6
6
  const {
7
7
  PRO_MONTHLY_PAYMENT_LINK,
8
8
  PRO_PRICE_LABEL,
9
- TEAM_PRICE_LABEL,
9
+ ENTERPRISE_PRICE_LABEL,
10
10
  } = require('./commercial-offer');
11
11
 
12
12
  const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
13
13
 
14
14
  // ──────────────────────────────────────────────────────────
15
- // Free tier: generous on captures (habit formation) and rules
16
- // (5 active gates), gated on Pro-only features (recall, search,
17
- // exports). Dashboard, exports, and unlimited rules drive Pro.
15
+ // Free tier: tight enough to create upgrade pressure after
16
+ // real usage. Captures and rules are capped so heavy users
17
+ // hit the wall within the first week, not the first quarter.
18
18
  // ──────────────────────────────────────────────────────────
19
19
  const FREE_TIER_LIMITS = {
20
- capture_feedback: { daily: Infinity, lifetime: Infinity, label: 'feedback captures' },
21
- prevention_rules: { daily: Infinity, lifetime: Infinity, label: 'prevention rules generated' },
20
+ capture_feedback: { daily: 2, lifetime: 10, label: 'feedback captures (2/day, 10 total on free)' },
21
+ prevention_rules: { daily: 2, lifetime: 3, label: 'prevention rules generated (2/day on free)' },
22
22
  recall: { daily: 0, lifetime: 0, label: 'recall queries (Pro only)' },
23
23
  search_lessons: { daily: 0, lifetime: 0, label: 'lesson searches (Pro only)' },
24
24
  search_thumbgate: { daily: 0, lifetime: 0, label: 'ThumbGate searches (Pro only)' },
25
25
  commerce_recall: { daily: 0, lifetime: 0, label: 'commerce recalls (Pro only)' },
26
26
  export_dpo: { daily: 0, lifetime: 0, label: 'DPO exports (Pro only)' },
27
27
  export_databricks: { daily: 0, lifetime: 0, label: 'Databricks exports (Pro only)' },
28
- construct_context_pack: { daily: Infinity, lifetime: Infinity, label: 'context packs' },
28
+ construct_context_pack: { daily: 3, lifetime: Infinity, label: 'context packs (3/day on free)' },
29
29
  };
30
30
 
31
- const FREE_TIER_MAX_GATES = 5; // 5 active prevention rules on free; Pro is unlimited
31
+ const FREE_TIER_MAX_GATES = 3; // 3 active prevention rules on free; Pro is unlimited
32
+ const FREE_TIER_DAILY_BLOCKS = 2; // 3 gate blocks/day on free; after limit, deny → warn + upgrade CTA
32
33
 
33
- const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${PRO_MONTHLY_PAYMENT_LINK}\n Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
34
+ const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${PRO_MONTHLY_PAYMENT_LINK}\n Enterprise: ${ENTERPRISE_PRICE_LABEL} after workflow qualification.`;
34
35
 
35
36
  const PAYWALL_MESSAGES = {
36
- prevention_rules: 'Free tier includes 5 active prevention rules. Promote more or unlock unlimited rules with Pro.',
37
+ capture_feedback: 'Free tier: 2 captures/day (10 total). Your feedback is stored locally upgrade to capture unlimited.',
38
+ prevention_rules: 'Free tier includes 3 active prevention rules and 2 rule generations/day. Upgrade to Pro for unlimited rules.',
37
39
  recall: 'Recall is a Pro feature. Your past feedback is stored locally — upgrade to search and reuse it.',
38
40
  search_lessons: 'Lesson search is a Pro feature. Upgrade to find patterns in your agent\'s mistakes.',
41
+ construct_context_pack: 'Free tier: 3 context packs/day. Upgrade to Pro for unlimited.',
39
42
  default: 'This feature requires Pro. Start Pro — card required; billed today.',
40
43
  };
41
44
 
45
+ const TRIAL_DAYS = 7;
46
+
47
+ function getInstallAgeDays() {
48
+ try {
49
+ const { INSTALL_ID_PATH } = require('./cli-telemetry');
50
+ if (!fs.existsSync(INSTALL_ID_PATH)) return null;
51
+ // Use mtimeMs — birthtimeMs is unreliable on Linux (ext4 doesn't backdate creation time).
52
+ // The install-id file is written once at install, so mtime == creation time in practice.
53
+ const stat = fs.statSync(INSTALL_ID_PATH);
54
+ const created = stat.mtimeMs || stat.birthtimeMs;
55
+ if (!Number.isFinite(created) || created <= 0) return null;
56
+ return (Date.now() - created) / (1000 * 60 * 60 * 24);
57
+ } catch (_) {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ function isInTrialPeriod() {
63
+ if (process.env.CI || process.env.GITHUB_ACTIONS) return false;
64
+ if (process.env.THUMBGATE_NO_TRIAL === '1') return false;
65
+ const age = getInstallAgeDays();
66
+ if (age === null) return false;
67
+ if (age < 0.0007) return false; // <1 minute old — just-created install, not a real trial
68
+ return age < TRIAL_DAYS;
69
+ }
70
+
71
+ function trialDaysRemaining() {
72
+ const age = getInstallAgeDays();
73
+ if (age === null) return 0;
74
+ return Math.max(0, Math.ceil(TRIAL_DAYS - age));
75
+ }
76
+
42
77
  function isProTier(authContext) {
43
78
  if (authContext && authContext.tier === 'pro') return true;
44
- if (process.env.THUMBGATE_API_KEY || process.env.THUMBGATE_PRO_MODE === '1' || process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
79
+ if (process.env.THUMBGATE_API_KEY) return true;
80
+ if (process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
45
81
  // Creator/dogfooding bypass: when the owner has the dev secret + bypass
46
82
  // configured (env or ~/.config/thumbgate/dev.json), treat the install as Pro
47
83
  // so marketing nudges and rate limits stop firing on the maintainer's own
@@ -57,6 +93,9 @@ function isProTier(authContext) {
57
93
  const { isProLicensed } = require('./license');
58
94
  if (isProLicensed()) return true;
59
95
  } catch (_) {}
96
+ // 7-day reverse trial: new installs get full Pro access, then hit a clear
97
+ // hosted-sync/unlimited-rules pay moment while the product is still fresh.
98
+ if (isInTrialPeriod()) return true;
60
99
  return false;
61
100
  }
62
101
 
@@ -114,12 +153,18 @@ function checkLimit(action, authContext) {
114
153
  const dailyCurrent = usage.counts[action] || 0;
115
154
  const lifetimeCurrent = usage.lifetime[action] || 0;
116
155
 
156
+ const isCapture = action === 'capture_feedback';
157
+ const monthlyLink = isCapture
158
+ ? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
159
+ : PRO_MONTHLY_PAYMENT_LINK;
160
+ const upgradeMessage = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${monthlyLink}\n Enterprise: ${ENTERPRISE_PRICE_LABEL} after workflow qualification.`;
161
+
117
162
  // Check lifetime limit first (the hard wall)
118
163
  if (lifetimeLimit !== Infinity && lifetimeCurrent >= lifetimeLimit) {
119
164
  const paywallMsg = PAYWALL_MESSAGES[action] || PAYWALL_MESSAGES.default;
120
165
  return {
121
166
  allowed: false,
122
- message: `${paywallMsg}\n\n${UPGRADE_MESSAGE}`,
167
+ message: `${paywallMsg}\n\n${upgradeMessage}`,
123
168
  used: lifetimeCurrent,
124
169
  limit: lifetimeLimit,
125
170
  limitType: 'lifetime',
@@ -128,9 +173,10 @@ function checkLimit(action, authContext) {
128
173
 
129
174
  // Check daily limit
130
175
  if (dailyLimit !== Infinity && dailyCurrent >= dailyLimit) {
176
+ const paywallMsg = PAYWALL_MESSAGES[action] || PAYWALL_MESSAGES.default;
131
177
  return {
132
178
  allowed: false,
133
- message: `Daily limit reached. ${UPGRADE_MESSAGE}`,
179
+ message: `Daily limit reached. ${paywallMsg}\n\n${upgradeMessage}`,
134
180
  used: dailyCurrent,
135
181
  limit: dailyLimit,
136
182
  limitType: 'daily',
@@ -179,12 +225,17 @@ function getUsage(action, authContext) {
179
225
  module.exports = {
180
226
  checkLimit,
181
227
  getUsage,
228
+ getInstallAgeDays,
182
229
  isProTier,
230
+ isInTrialPeriod,
231
+ trialDaysRemaining,
183
232
  loadUsage,
184
233
  saveUsage,
185
234
  todayKey,
186
235
  FREE_TIER_LIMITS,
187
236
  FREE_TIER_MAX_GATES,
237
+ FREE_TIER_DAILY_BLOCKS,
238
+ TRIAL_DAYS,
188
239
  UPGRADE_MESSAGE,
189
240
  PAYWALL_MESSAGES,
190
241
  USAGE_FILE,
@@ -55,6 +55,11 @@ const BASH_SECRET_READ_PREFIXES = [
55
55
  ];
56
56
 
57
57
  const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
58
+ const SAFE_SECRET_STORAGE_DIRS = [
59
+ '.resume_secrets',
60
+ '.thumbgate/secrets',
61
+ '.config/thumbgate',
62
+ ];
58
63
 
59
64
  function redactText(text) {
60
65
  if (!text) return '';
@@ -172,6 +177,13 @@ function heuristicScanText(text, source = 'text') {
172
177
  pattern.regex.lastIndex = 0;
173
178
  let match = pattern.regex.exec(input);
174
179
  while (match) {
180
+ // Safe test key bypass
181
+ const matchedString = match[0].toLowerCase();
182
+ if (pattern.id === 'generic_assignment' && (matchedString.includes('sk_test_') || matchedString.includes('test_token'))) {
183
+ match = pattern.regex.exec(input);
184
+ continue;
185
+ }
186
+
175
187
  findings.push({
176
188
  id: pattern.id,
177
189
  label: pattern.label,
@@ -295,6 +307,26 @@ function resolvePathToken(token, cwd) {
295
307
  return path.join(cwd || process.cwd(), normalized);
296
308
  }
297
309
 
310
+ function normalizePathForPolicy(filePath) {
311
+ return path.resolve(String(filePath || '').replace(/^~(?=\/|$)/, os.homedir()));
312
+ }
313
+
314
+ function isSafeSecretStoragePath(filePath) {
315
+ if (!filePath) return false;
316
+ const normalized = normalizePathForPolicy(filePath);
317
+ const home = normalizePathForPolicy(os.homedir());
318
+ return SAFE_SECRET_STORAGE_DIRS.some((dir) => {
319
+ const allowedRoot = path.join(home, dir);
320
+ return normalized === allowedRoot || normalized.startsWith(`${allowedRoot}${path.sep}`);
321
+ });
322
+ }
323
+
324
+ function isSafeSecretStorageWrite(toolName, toolInput = {}, cwd = process.cwd()) {
325
+ if (!EDIT_LIKE_TOOLS.has(toolName)) return false;
326
+ const paths = getToolInputPaths(toolInput, cwd);
327
+ return paths.length > 0 && paths.every((filePath) => isSafeSecretStoragePath(filePath));
328
+ }
329
+
298
330
  function scanBashCommand(command, options = {}) {
299
331
  const cwd = options.cwd || process.cwd();
300
332
  const findings = [];
@@ -347,6 +379,7 @@ function scanHookInput(input = {}, options = {}) {
347
379
  let provider = resolveProvider(options.provider);
348
380
  let commandHash = null;
349
381
  let fileHashes = [];
382
+ const safeSecretStorageWrite = isSafeSecretStorageWrite(toolName, toolInput, cwd);
350
383
 
351
384
  const contentFields = [
352
385
  toolInput.content,
@@ -376,11 +409,13 @@ function scanHookInput(input = {}, options = {}) {
376
409
  }
377
410
  }
378
411
 
379
- for (const content of contentFields) {
380
- const result = scanText(content, { provider, source: 'tool_input' });
381
- if (result.detected) {
382
- provider = result.provider;
383
- findings.push(...result.findings);
412
+ if (!safeSecretStorageWrite) {
413
+ for (const content of contentFields) {
414
+ const result = scanText(content, { provider, source: 'tool_input' });
415
+ if (result.detected) {
416
+ provider = result.provider;
417
+ findings.push(...result.findings);
418
+ }
384
419
  }
385
420
  }
386
421
 
@@ -402,6 +437,8 @@ function buildSafeSummary(findings, prefix) {
402
437
  module.exports = {
403
438
  SECRET_PATTERNS,
404
439
  SECRET_FILE_PATTERNS,
440
+ SAFE_SECRET_STORAGE_DIRS,
441
+ EDIT_LIKE_TOOLS,
405
442
  redactText,
406
443
  resolveProvider,
407
444
  scanText,
@@ -409,6 +446,8 @@ module.exports = {
409
446
  scanBashCommand,
410
447
  scanHookInput,
411
448
  classifySecretPath,
449
+ isSafeSecretStoragePath,
450
+ isSafeSecretStorageWrite,
412
451
  buildSafeSummary,
413
452
  tokenizeCommand,
414
453
  };