thumbgate 0.9.9

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 (369) hide show
  1. package/.claude-plugin/README.md +134 -0
  2. package/.claude-plugin/bundle/icon.png +0 -0
  3. package/.claude-plugin/bundle/icon.svg +18 -0
  4. package/.claude-plugin/bundle/server/index.js +24 -0
  5. package/.claude-plugin/marketplace.json +36 -0
  6. package/.claude-plugin/plugin.json +21 -0
  7. package/.well-known/mcp/server-card.json +231 -0
  8. package/LICENSE +21 -0
  9. package/README.md +375 -0
  10. package/adapters/README.md +9 -0
  11. package/adapters/amp/skills/rlhf-feedback/SKILL.md +22 -0
  12. package/adapters/chatgpt/INSTALL.md +83 -0
  13. package/adapters/chatgpt/openapi.yaml +1281 -0
  14. package/adapters/claude/.mcp.json +14 -0
  15. package/adapters/codex/config.toml +9 -0
  16. package/adapters/gemini/function-declarations.json +224 -0
  17. package/adapters/mcp/server-stdio.js +788 -0
  18. package/adapters/opencode/opencode.json +15 -0
  19. package/bin/cli.js +1483 -0
  20. package/bin/memory.sh +64 -0
  21. package/bin/obsidian-sync.sh +20 -0
  22. package/bin/postinstall.js +37 -0
  23. package/config/build-metadata.json +4 -0
  24. package/config/e2e-critical-flows.json +45 -0
  25. package/config/gate-templates.json +77 -0
  26. package/config/gates/claim-verification.json +29 -0
  27. package/config/gates/computer-use.json +39 -0
  28. package/config/gates/default.json +117 -0
  29. package/config/github-about.json +25 -0
  30. package/config/mcp-allowlists.json +135 -0
  31. package/config/model-tiers.json +33 -0
  32. package/config/partner-routing.json +132 -0
  33. package/config/policy-bundles/constrained-v1.json +64 -0
  34. package/config/policy-bundles/default-v1.json +91 -0
  35. package/config/rubrics/default-v1.json +52 -0
  36. package/config/skill-packs/react-testing.json +23 -0
  37. package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
  38. package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
  39. package/config/skill-specs/pr-reviewer.json +9 -0
  40. package/config/skill-specs/release-status.json +9 -0
  41. package/config/skill-specs/ticket-triage.json +9 -0
  42. package/config/subagent-profiles.json +32 -0
  43. package/config/tessl-tiles.json +29 -0
  44. package/config/thumbgate-settings.managed.json +12 -0
  45. package/openapi/openapi.yaml +1281 -0
  46. package/package.json +286 -0
  47. package/plugins/amp-skill/INSTALL.md +52 -0
  48. package/plugins/amp-skill/SKILL.md +64 -0
  49. package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
  50. package/plugins/claude-codex-bridge/.mcp.json +12 -0
  51. package/plugins/claude-codex-bridge/INSTALL.md +43 -0
  52. package/plugins/claude-codex-bridge/README.md +46 -0
  53. package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
  54. package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
  55. package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
  56. package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
  57. package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
  58. package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
  59. package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
  60. package/plugins/claude-skill/INSTALL.md +55 -0
  61. package/plugins/claude-skill/SKILL.md +46 -0
  62. package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
  63. package/plugins/codex-profile/.mcp.json +12 -0
  64. package/plugins/codex-profile/AGENTS.md +20 -0
  65. package/plugins/codex-profile/INSTALL.md +66 -0
  66. package/plugins/codex-profile/README.md +37 -0
  67. package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
  68. package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
  69. package/plugins/cursor-marketplace/LICENSE +21 -0
  70. package/plugins/cursor-marketplace/README.md +124 -0
  71. package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
  72. package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
  73. package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
  74. package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
  75. package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
  76. package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
  77. package/plugins/cursor-marketplace/mcp.json +12 -0
  78. package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
  79. package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
  80. package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
  81. package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
  82. package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
  83. package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
  84. package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
  85. package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
  86. package/plugins/gemini-extension/INSTALL.md +92 -0
  87. package/plugins/gemini-extension/gemini_prompt.txt +14 -0
  88. package/plugins/gemini-extension/tool_contract.json +45 -0
  89. package/plugins/opencode-profile/INSTALL.md +57 -0
  90. package/public/assets/instagram-card.png +0 -0
  91. package/public/assets/tiktok-agent-memory.mp4 +0 -0
  92. package/public/blog.html +400 -0
  93. package/public/dashboard.html +1093 -0
  94. package/public/guide.html +317 -0
  95. package/public/index.html +1195 -0
  96. package/public/learn/agent-harness-pattern.html +180 -0
  97. package/public/learn/ai-agent-persistent-memory.html +202 -0
  98. package/public/learn/learn.css +45 -0
  99. package/public/learn/mcp-pre-action-gates-explained.html +172 -0
  100. package/public/learn/stop-ai-agent-force-push.html +134 -0
  101. package/public/learn/vibe-coding-safety-net.html +142 -0
  102. package/public/learn.html +213 -0
  103. package/public/lessons.html +650 -0
  104. package/public/vercel.json +8 -0
  105. package/scripts/__pycache__/train_from_feedback.cpython-314.pyc +0 -0
  106. package/scripts/a2ui-engine.js +73 -0
  107. package/scripts/access-anomaly-detector.js +12 -0
  108. package/scripts/adk-consolidator.js +266 -0
  109. package/scripts/agent-readiness.js +220 -0
  110. package/scripts/agent-security-hardening.js +227 -0
  111. package/scripts/agentic-data-pipeline.js +847 -0
  112. package/scripts/analytics-report.js +328 -0
  113. package/scripts/analytics-window.js +158 -0
  114. package/scripts/async-job-runner.js +1001 -0
  115. package/scripts/audit-trail.js +398 -0
  116. package/scripts/auto-promote-gates.js +293 -0
  117. package/scripts/auto-wire-hooks.js +316 -0
  118. package/scripts/autonomous-sales-agent.js +39 -0
  119. package/scripts/autoresearch-runner.js +216 -0
  120. package/scripts/background-agent-governance.js +237 -0
  121. package/scripts/behavioral-extraction.js +93 -0
  122. package/scripts/belief-update.js +84 -0
  123. package/scripts/billing.js +2438 -0
  124. package/scripts/bot-detector.js +50 -0
  125. package/scripts/budget-guard.js +173 -0
  126. package/scripts/build-claude-mcpb.js +189 -0
  127. package/scripts/build-metadata.js +97 -0
  128. package/scripts/check-congruence.js +322 -0
  129. package/scripts/cli-feedback.js +135 -0
  130. package/scripts/cli-telemetry.js +87 -0
  131. package/scripts/cloudflare-dynamic-sandbox.js +315 -0
  132. package/scripts/code-reasoning.js +350 -0
  133. package/scripts/codegraph-context.js +466 -0
  134. package/scripts/commercial-offer.js +56 -0
  135. package/scripts/computer-use-firewall.js +250 -0
  136. package/scripts/context-engine.js +694 -0
  137. package/scripts/contextfs.js +1287 -0
  138. package/scripts/conversation-context.js +119 -0
  139. package/scripts/creator-campaigns.js +239 -0
  140. package/scripts/daemon-manager.js +108 -0
  141. package/scripts/daily-digest.js +11 -0
  142. package/scripts/dashboard-render-spec.js +395 -0
  143. package/scripts/dashboard.js +1058 -0
  144. package/scripts/data-governance.js +173 -0
  145. package/scripts/delegation-runtime.js +900 -0
  146. package/scripts/deploy-gcp.sh +44 -0
  147. package/scripts/deploy-policy.js +231 -0
  148. package/scripts/disagreement-mining.js +315 -0
  149. package/scripts/dispatch-brief.js +159 -0
  150. package/scripts/distribution-surfaces.js +44 -0
  151. package/scripts/dpo-optimizer.js +206 -0
  152. package/scripts/ensure-repo-bootstrap.js +129 -0
  153. package/scripts/ephemeral-agent-store.js +219 -0
  154. package/scripts/eval-harness.js +56 -0
  155. package/scripts/evolution-state.js +241 -0
  156. package/scripts/experiment-tracker.js +267 -0
  157. package/scripts/export-databricks-bundle.js +242 -0
  158. package/scripts/export-dpo-pairs.js +344 -0
  159. package/scripts/export-kto-pairs.js +309 -0
  160. package/scripts/export-training.js +450 -0
  161. package/scripts/failure-diagnostics.js +558 -0
  162. package/scripts/feedback-attribution.js +313 -0
  163. package/scripts/feedback-fallback.js +110 -0
  164. package/scripts/feedback-history-distiller.js +391 -0
  165. package/scripts/feedback-inbox-read.js +162 -0
  166. package/scripts/feedback-loop.js +1887 -0
  167. package/scripts/feedback-paths.js +145 -0
  168. package/scripts/feedback-quality.js +139 -0
  169. package/scripts/feedback-root-consolidator.js +238 -0
  170. package/scripts/feedback-schema.js +426 -0
  171. package/scripts/feedback-session.js +286 -0
  172. package/scripts/feedback-to-memory.js +185 -0
  173. package/scripts/feedback-to-rules.js +164 -0
  174. package/scripts/filesystem-search.js +405 -0
  175. package/scripts/funnel-analytics.js +35 -0
  176. package/scripts/gate-satisfy.js +42 -0
  177. package/scripts/gate-stats.js +116 -0
  178. package/scripts/gate-templates.js +70 -0
  179. package/scripts/gates-engine.js +816 -0
  180. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  181. package/scripts/generate-pretool-hook.sh +40 -0
  182. package/scripts/github-about.js +350 -0
  183. package/scripts/github-outreach.js +65 -0
  184. package/scripts/gtm-revenue-loop.js +520 -0
  185. package/scripts/hallucination-detector.js +226 -0
  186. package/scripts/hf-papers.js +317 -0
  187. package/scripts/history-distiller.js +200 -0
  188. package/scripts/hook-auto-capture.sh +100 -0
  189. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  190. package/scripts/hook-stop-self-score.sh +51 -0
  191. package/scripts/hook-stop-verify-deploy.sh +31 -0
  192. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  193. package/scripts/hook-verify-before-done.sh +20 -0
  194. package/scripts/hosted-config.js +156 -0
  195. package/scripts/hybrid-feedback-context.js +675 -0
  196. package/scripts/install-mcp.js +159 -0
  197. package/scripts/intent-router.js +392 -0
  198. package/scripts/internal-agent-bootstrap.js +490 -0
  199. package/scripts/jsonl-watcher.js +155 -0
  200. package/scripts/lesson-db.js +613 -0
  201. package/scripts/lesson-inference.js +310 -0
  202. package/scripts/lesson-retrieval.js +95 -0
  203. package/scripts/lesson-rotation.js +137 -0
  204. package/scripts/lesson-search.js +644 -0
  205. package/scripts/lesson-synthesis.js +196 -0
  206. package/scripts/license.js +50 -0
  207. package/scripts/local-model-profile.js +384 -0
  208. package/scripts/markdown-escape.js +12 -0
  209. package/scripts/marketing-experiment.js +671 -0
  210. package/scripts/mcp-config.js +149 -0
  211. package/scripts/mcp-policy.js +99 -0
  212. package/scripts/memalign-recall.js +111 -0
  213. package/scripts/memory-firewall.js +222 -0
  214. package/scripts/memory-migration.js +296 -0
  215. package/scripts/meta-policy.js +190 -0
  216. package/scripts/metered-billing.js +16 -0
  217. package/scripts/model-tier-router.js +301 -0
  218. package/scripts/money-watcher.js +71 -0
  219. package/scripts/multi-hop-recall.js +240 -0
  220. package/scripts/natural-language-harness.js +330 -0
  221. package/scripts/obsidian-export.js +713 -0
  222. package/scripts/operational-dashboard.js +103 -0
  223. package/scripts/operational-summary.js +93 -0
  224. package/scripts/optimize-context.js +17 -0
  225. package/scripts/org-dashboard.js +201 -0
  226. package/scripts/partner-orchestration.js +146 -0
  227. package/scripts/per-step-scoring.js +165 -0
  228. package/scripts/perplexity-marketing.js +466 -0
  229. package/scripts/pii-scanner.js +153 -0
  230. package/scripts/plan-gate.js +154 -0
  231. package/scripts/post-everywhere.js +308 -0
  232. package/scripts/post-to-x-retry.sh +22 -0
  233. package/scripts/post-to-x.js +369 -0
  234. package/scripts/pr-manager.js +236 -0
  235. package/scripts/predictive-insights.js +356 -0
  236. package/scripts/principle-extractor.js +162 -0
  237. package/scripts/pro-features.js +40 -0
  238. package/scripts/pro-local-dashboard.js +174 -0
  239. package/scripts/problem-detail.js +53 -0
  240. package/scripts/product-feedback.js +134 -0
  241. package/scripts/profile-router.js +245 -0
  242. package/scripts/prompt-dlp.js +221 -0
  243. package/scripts/prompt-guard.js +83 -0
  244. package/scripts/prove-adapters.js +863 -0
  245. package/scripts/prove-attribution.js +365 -0
  246. package/scripts/prove-automation.js +653 -0
  247. package/scripts/prove-autoresearch.js +304 -0
  248. package/scripts/prove-claim-verification.js +277 -0
  249. package/scripts/prove-cloudflare-sandbox.js +163 -0
  250. package/scripts/prove-data-pipeline.js +410 -0
  251. package/scripts/prove-data-quality.js +227 -0
  252. package/scripts/prove-evolution.js +352 -0
  253. package/scripts/prove-harnesses.js +287 -0
  254. package/scripts/prove-intelligence.js +259 -0
  255. package/scripts/prove-lancedb.js +371 -0
  256. package/scripts/prove-local-intelligence.js +342 -0
  257. package/scripts/prove-loop-closure.js +263 -0
  258. package/scripts/prove-predictive-insights.js +357 -0
  259. package/scripts/prove-runtime.js +350 -0
  260. package/scripts/prove-seo-gsd.js +234 -0
  261. package/scripts/prove-settings.js +279 -0
  262. package/scripts/prove-subway-upgrades.js +277 -0
  263. package/scripts/prove-tessl.js +229 -0
  264. package/scripts/prove-training-export.js +327 -0
  265. package/scripts/prove-workflow-contract.js +116 -0
  266. package/scripts/prove-xmemory.js +332 -0
  267. package/scripts/publish-decision.js +133 -0
  268. package/scripts/pulse.js +80 -0
  269. package/scripts/rate-limiter.js +125 -0
  270. package/scripts/reddit-dm-outreach.js +182 -0
  271. package/scripts/reddit-monitor-cron.sh +26 -0
  272. package/scripts/reflector-agent.js +221 -0
  273. package/scripts/reminder-engine.js +132 -0
  274. package/scripts/revenue-status.js +472 -0
  275. package/scripts/risk-scorer.js +459 -0
  276. package/scripts/rlaif-self-audit.js +129 -0
  277. package/scripts/rlhf_session_start.sh +32 -0
  278. package/scripts/rubric-engine.js +230 -0
  279. package/scripts/schedule-manager.js +251 -0
  280. package/scripts/secret-scanner.js +414 -0
  281. package/scripts/self-heal.js +147 -0
  282. package/scripts/self-healing-check.js +188 -0
  283. package/scripts/semantic-layer.js +98 -0
  284. package/scripts/seo-gsd.js +1153 -0
  285. package/scripts/settings-hierarchy.js +214 -0
  286. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  287. package/scripts/skill-exporter.js +262 -0
  288. package/scripts/skill-generator.js +446 -0
  289. package/scripts/skill-materializer.js +134 -0
  290. package/scripts/skill-packs.js +136 -0
  291. package/scripts/skill-proposer.js +99 -0
  292. package/scripts/skill-quality-tracker.js +282 -0
  293. package/scripts/slo-alert-engine.js +14 -0
  294. package/scripts/slow-loop.js +72 -0
  295. package/scripts/social-analytics/db/schema.sql +32 -0
  296. package/scripts/social-analytics/db/social-analytics.db +0 -0
  297. package/scripts/social-analytics/digest.js +256 -0
  298. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  299. package/scripts/social-analytics/instagram-thumbgate-post.js +107 -0
  300. package/scripts/social-analytics/load-env.js +46 -0
  301. package/scripts/social-analytics/mcp-server.js +289 -0
  302. package/scripts/social-analytics/normalizer.js +580 -0
  303. package/scripts/social-analytics/notify.js +162 -0
  304. package/scripts/social-analytics/poll-all.js +92 -0
  305. package/scripts/social-analytics/pollers/github.js +195 -0
  306. package/scripts/social-analytics/pollers/instagram.js +253 -0
  307. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  308. package/scripts/social-analytics/pollers/plausible.js +247 -0
  309. package/scripts/social-analytics/pollers/reddit.js +306 -0
  310. package/scripts/social-analytics/pollers/threads.js +233 -0
  311. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  312. package/scripts/social-analytics/pollers/x.js +227 -0
  313. package/scripts/social-analytics/pollers/youtube.js +304 -0
  314. package/scripts/social-analytics/pollers/zernio.js +183 -0
  315. package/scripts/social-analytics/publish-instagram-thumbgate.js +98 -0
  316. package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
  317. package/scripts/social-analytics/publishers/devto.js +122 -0
  318. package/scripts/social-analytics/publishers/instagram.js +317 -0
  319. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  320. package/scripts/social-analytics/publishers/reddit.js +390 -0
  321. package/scripts/social-analytics/publishers/threads.js +275 -0
  322. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  323. package/scripts/social-analytics/publishers/x.js +259 -0
  324. package/scripts/social-analytics/publishers/youtube.js +223 -0
  325. package/scripts/social-analytics/publishers/zernio.js +378 -0
  326. package/scripts/social-analytics/run-digest.js +34 -0
  327. package/scripts/social-analytics/store.js +257 -0
  328. package/scripts/social-analytics/utm.js +143 -0
  329. package/scripts/social-pipeline.js +2628 -0
  330. package/scripts/social-quality-gate.js +18 -0
  331. package/scripts/social-reply-monitor.js +445 -0
  332. package/scripts/status-dashboard.js +155 -0
  333. package/scripts/statusline-lesson.js +16 -0
  334. package/scripts/statusline-tower.js +8 -0
  335. package/scripts/statusline.sh +116 -0
  336. package/scripts/stripe-live-status.js +115 -0
  337. package/scripts/subagent-profiles.js +79 -0
  338. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  339. package/scripts/sync-github-about.js +52 -0
  340. package/scripts/sync-version.js +447 -0
  341. package/scripts/synthetic-dpo.js +234 -0
  342. package/scripts/telemetry-analytics.js +821 -0
  343. package/scripts/tessl-export.js +371 -0
  344. package/scripts/test-coverage.js +120 -0
  345. package/scripts/thompson-sampling.js +417 -0
  346. package/scripts/thumbgate-search.js +189 -0
  347. package/scripts/tool-kpi-tracker.js +12 -0
  348. package/scripts/tool-registry.js +811 -0
  349. package/scripts/train_from_feedback.py +933 -0
  350. package/scripts/user-profile.js +78 -0
  351. package/scripts/validate-feedback.js +581 -0
  352. package/scripts/validate-workflow-contract.js +287 -0
  353. package/scripts/vector-store.js +197 -0
  354. package/scripts/verification-loop.js +291 -0
  355. package/scripts/verify-obsidian-setup.sh +269 -0
  356. package/scripts/verify-run.js +269 -0
  357. package/scripts/webhook-delivery.js +62 -0
  358. package/scripts/weekly-auto-post.js +124 -0
  359. package/scripts/workflow-runs.js +154 -0
  360. package/scripts/workflow-sprint-intake.js +475 -0
  361. package/scripts/workspace-evolver.js +374 -0
  362. package/scripts/x-autonomous-marketing.js +139 -0
  363. package/scripts/xmemory-lite.js +405 -0
  364. package/skills/agent-memory/SKILL.md +97 -0
  365. package/skills/rlhf-feedback/SKILL.md +49 -0
  366. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  367. package/skills/solve-architecture-autonomy/tool.js +33 -0
  368. package/skills/thumbgate/SKILL.md +114 -0
  369. package/src/api/server.js +4206 -0
package/bin/cli.js ADDED
@@ -0,0 +1,1483 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * thumbgate CLI
4
+ *
5
+ * Usage:
6
+ * npx thumbgate init # scaffold .thumbgate/ config + .mcp.json
7
+ * npx thumbgate init --wire-hooks # wire hooks only (auto-detect agent)
8
+ * npx thumbgate init --agent claude-code # scaffold + wire hooks for specific agent
9
+ * npx thumbgate capture # capture feedback
10
+ * npx thumbgate export-dpo # export DPO training pairs
11
+ * npx thumbgate export-databricks # export Databricks-ready analytics bundle
12
+ * npx thumbgate stats # feedback analytics + Revenue-at-Risk
13
+ * npx thumbgate cfo # local operational billing summary
14
+ * npx thumbgate pro # upgrade to Context Gateway
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const crypto = require('crypto');
22
+ const { execSync } = require('child_process');
23
+ const { resolveMcpEntry } = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
24
+ const { trackEvent } = require(path.join(__dirname, '..', 'scripts', 'cli-telemetry'));
25
+ const {
26
+ PRO_MONTHLY_PAYMENT_LINK,
27
+ PRO_PRICE_LABEL,
28
+ } = require(path.join(__dirname, '..', 'scripts', 'commercial-offer'));
29
+
30
+ const COMMAND = process.argv[2];
31
+ const CWD = process.cwd();
32
+ const PKG_ROOT = path.join(__dirname, '..');
33
+
34
+ const PRO_URL = 'https://thumbgate-production.up.railway.app';
35
+ const PRO_CHECKOUT_URL = PRO_MONTHLY_PAYMENT_LINK;
36
+
37
+ function upgradeNudge() {
38
+ if (process.env.THUMBGATE_NO_NUDGE === '1') return;
39
+ try {
40
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
41
+ if (isProTier()) return;
42
+ } catch (_) { return; }
43
+ process.stderr.write(
44
+ `\n Unlock Pro: unlimited gates, DPO export, searchable dashboard — ${PRO_PRICE_LABEL}\n` +
45
+ ` ${PRO_CHECKOUT_URL}\n\n`
46
+ );
47
+ }
48
+
49
+ function appendLocalTelemetry(payload) {
50
+ try {
51
+ const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
52
+ const { appendTelemetryPing } = require(path.join(PKG_ROOT, 'scripts', 'telemetry-analytics'));
53
+ const { FEEDBACK_DIR } = getFeedbackPaths();
54
+ appendTelemetryPing(FEEDBACK_DIR, payload);
55
+ } catch (_) { /* telemetry is best-effort */ }
56
+ }
57
+
58
+ function telemetryPing(installId) {
59
+ if (process.env.THUMBGATE_NO_TELEMETRY === '1') return;
60
+ const payloadObject = {
61
+ installId,
62
+ eventType: 'cli_init',
63
+ clientType: 'cli',
64
+ source: 'cli',
65
+ version: pkgVersion(),
66
+ platform: process.platform,
67
+ nodeVersion: process.version,
68
+ timestamp: new Date().toISOString(),
69
+ };
70
+ appendLocalTelemetry(payloadObject);
71
+ const apiUrl = process.env.THUMBGATE_API_URL || 'https://thumbgate-production.up.railway.app';
72
+ const payload = JSON.stringify(payloadObject);
73
+ try {
74
+ const url = new URL('/v1/telemetry/ping', apiUrl);
75
+ const mod = url.protocol === 'https:' ? require('https') : require('http');
76
+ const req = mod.request(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: 3000 }, () => {});
77
+ req.on('error', () => {});
78
+ req.on('timeout', () => { req.destroy(); });
79
+ req.end(payload);
80
+ } catch (_) { /* telemetry is best-effort */ }
81
+ }
82
+
83
+ function proNudge(context) {
84
+ if (process.env.THUMBGATE_NO_NUDGE === '1') return;
85
+ const messages = [
86
+ `\n šŸ’” Unlock Pro (${PRO_PRICE_LABEL}): searchable dashboard, DPO export, multi-repo sync\n ${PRO_CHECKOUT_URL}\n`,
87
+ `\n šŸ’” Pro tip: export your feedback as DPO training pairs to improve your models.\n Get Pro: ${PRO_CHECKOUT_URL}\n`,
88
+ `\n šŸ’” ThumbGate Pro: search, edit, and sync lessons across repos. ${PRO_PRICE_LABEL}.\n ${PRO_CHECKOUT_URL}\n`,
89
+ ];
90
+ // Rotate message daily — no Math.random (security policy)
91
+ const msg = messages[Math.floor(Date.now() / 86400000) % messages.length];
92
+ process.stderr.write(msg);
93
+ }
94
+
95
+ function limitNudge(action) {
96
+ if (process.env.THUMBGATE_NO_NUDGE === '1') return;
97
+ process.stderr.write(
98
+ `\n āš ļø Free tier: ${action} daily limit reached.\n` +
99
+ ` Upgrade to Pro for unlimited usage — ${PRO_PRICE_LABEL}:\n` +
100
+ ` ${PRO_CHECKOUT_URL}\n\n`
101
+ );
102
+ }
103
+
104
+ function parseArgs(argv) {
105
+ const args = {};
106
+ argv.forEach((arg) => {
107
+ if (!arg.startsWith('--')) return;
108
+ const [key, ...rest] = arg.slice(2).split('=');
109
+ args[key] = rest.length ? rest.join('=') : true;
110
+ });
111
+ return args;
112
+ }
113
+
114
+ function pkgVersion() {
115
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
116
+ return pkg.version;
117
+ }
118
+
119
+ // --- Platform auto-detection helpers ---
120
+
121
+ const HOME = process.env.HOME || process.env.USERPROFILE || '';
122
+ const MCP_SERVER_NAME = 'rlhf';
123
+ const LEGACY_MCP_SERVER_NAMES = ['rlhf', 'thumbgate', 'rlhf_feedback_loop'];
124
+
125
+ function mcpEntriesMatch(entry, expectedEntry) {
126
+ return Boolean(
127
+ entry &&
128
+ expectedEntry &&
129
+ entry.command === expectedEntry.command &&
130
+ Array.isArray(entry.args) &&
131
+ Array.isArray(expectedEntry.args) &&
132
+ entry.args.length === expectedEntry.args.length &&
133
+ entry.args.every((arg, index) => arg === expectedEntry.args[index])
134
+ );
135
+ }
136
+
137
+ function escapeRegExp(value) {
138
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
139
+ }
140
+
141
+ function formatTomlStringArray(values) {
142
+ return `[${values.map((value) => JSON.stringify(value)).join(', ')}]`;
143
+ }
144
+
145
+ function canonicalMcpEntry(scope = 'project') {
146
+ return resolveMcpEntry({
147
+ pkgRoot: PKG_ROOT,
148
+ pkgVersion: pkgVersion(),
149
+ scope,
150
+ targetDir: CWD,
151
+ });
152
+ }
153
+
154
+ function mcpSectionBlock(name = MCP_SERVER_NAME, scope = 'project') {
155
+ const entry = canonicalMcpEntry(scope);
156
+ return `[mcp_servers.${name}]\ncommand = "${entry.command}"\nargs = ${formatTomlStringArray(entry.args)}\n`;
157
+ }
158
+
159
+ function mcpSectionRegex(name) {
160
+ return new RegExp(
161
+ `^\\[mcp_servers\\.${escapeRegExp(name)}\\]\\n(?:^(?!\\[).*(?:\\n|$))*`,
162
+ 'm'
163
+ );
164
+ }
165
+
166
+ function upsertCodexServerConfig(content) {
167
+ const canonicalBlock = mcpSectionBlock(MCP_SERVER_NAME, 'home');
168
+ const sections = LEGACY_MCP_SERVER_NAMES.map((name) => ({
169
+ name,
170
+ regex: mcpSectionRegex(name),
171
+ }));
172
+ const matches = sections
173
+ .map((section) => ({ ...section, match: content.match(section.regex) }))
174
+ .filter((section) => section.match);
175
+
176
+ if (matches.length === 0) {
177
+ const prefix = content.trimEnd();
178
+ return {
179
+ changed: true,
180
+ content: `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}`,
181
+ };
182
+ }
183
+
184
+ let nextContent = content;
185
+ let changed = false;
186
+ let canonicalPresent = false;
187
+
188
+ for (const section of matches) {
189
+ const normalized = canonicalBlock;
190
+ const current = section.match[0];
191
+
192
+ if (section.name === MCP_SERVER_NAME) {
193
+ canonicalPresent = true;
194
+ if (current !== normalized) {
195
+ nextContent = nextContent.replace(section.regex, normalized);
196
+ changed = true;
197
+ }
198
+ continue;
199
+ }
200
+
201
+ nextContent = nextContent.replace(section.regex, '');
202
+ changed = true;
203
+ }
204
+
205
+ if (!canonicalPresent) {
206
+ const prefix = nextContent.trimEnd();
207
+ nextContent = `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}`;
208
+ changed = true;
209
+ }
210
+
211
+ return {
212
+ changed,
213
+ content: nextContent.endsWith('\n') ? nextContent : `${nextContent}\n`,
214
+ };
215
+ }
216
+
217
+ function mergeMcpJson(filePath, label, scope = 'project') {
218
+ const canonicalEntry = canonicalMcpEntry(scope);
219
+ if (!fs.existsSync(filePath)) {
220
+ const dir = path.dirname(filePath);
221
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
222
+ fs.writeFileSync(filePath, JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: canonicalEntry } }, null, 2) + '\n');
223
+ console.log(` ${label}: wrote ${path.relative(CWD, filePath)}`);
224
+ return true;
225
+ }
226
+ const existing = JSON.parse(fs.readFileSync(filePath, 'utf8'));
227
+ existing.mcpServers = existing.mcpServers || {};
228
+
229
+ let changed = false;
230
+ const currentEntry = existing.mcpServers[MCP_SERVER_NAME];
231
+ if (!mcpEntriesMatch(currentEntry, canonicalEntry)) {
232
+ existing.mcpServers[MCP_SERVER_NAME] = canonicalEntry;
233
+ changed = true;
234
+ }
235
+
236
+ for (const legacyName of LEGACY_MCP_SERVER_NAMES) {
237
+ if (legacyName === MCP_SERVER_NAME) continue;
238
+ if (Object.prototype.hasOwnProperty.call(existing.mcpServers, legacyName)) {
239
+ delete existing.mcpServers[legacyName];
240
+ changed = true;
241
+ }
242
+ }
243
+
244
+ if (!changed) return false;
245
+
246
+ fs.writeFileSync(filePath, JSON.stringify(existing, null, 2) + '\n');
247
+ console.log(` ${label}: updated ${path.relative(CWD, filePath)}`);
248
+ return true;
249
+ }
250
+
251
+ function detectPlatform(name, checks) {
252
+ for (const check of checks) {
253
+ try { if (check()) return true; } catch (_) {}
254
+ }
255
+ return false;
256
+ }
257
+
258
+ function whichExists(cmd) {
259
+ try { execSync(`which ${cmd}`, { stdio: 'pipe' }); return true; } catch (_) { return false; }
260
+ }
261
+
262
+ function setupClaude() {
263
+ const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code', 'project');
264
+
265
+ // Upsert Stop hook into .claude/settings.json for autonomous self-scoring
266
+ const settingsPath = path.join(CWD, '.claude', 'settings.json');
267
+ const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
268
+
269
+ let settings = { hooks: {} };
270
+ if (fs.existsSync(settingsPath)) {
271
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
272
+ }
273
+ settings.hooks = settings.hooks || {};
274
+
275
+ const stopAlreadyPresent = (settings.hooks.Stop || [])
276
+ .some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
277
+
278
+ let hooksChanged = false;
279
+ if (!stopAlreadyPresent) {
280
+ settings.hooks.Stop = settings.hooks.Stop || [];
281
+ settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
282
+ hooksChanged = true;
283
+ console.log(' Claude Code: installed Stop hook');
284
+ }
285
+
286
+ // Upsert PostToolUse hook for ThumbGate statusline cache updates
287
+ const cacheHookCommand = 'node node_modules/thumbgate/scripts/hook-thumbgate-cache-updater.js';
288
+ const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
289
+ .some(entry => (entry.hooks || []).some(h => h.command && h.command.includes('hook-thumbgate-cache-updater')));
290
+
291
+ if (!cacheAlreadyPresent) {
292
+ settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
293
+ settings.hooks.PostToolUse.push({
294
+ matcher: 'mcp__rlhf__feedback_stats|mcp__rlhf__dashboard',
295
+ hooks: [{ type: 'command', command: cacheHookCommand }]
296
+ });
297
+ hooksChanged = true;
298
+ console.log(' Claude Code: installed ThumbGate cache updater hook');
299
+ }
300
+
301
+ // Upsert statusLine for ThumbGate feedback display
302
+ const statuslineScript = path.join('node_modules', 'thumbgate', 'scripts', 'statusline.sh');
303
+ if (!settings.statusLine) {
304
+ settings.statusLine = { type: 'command', command: statuslineScript };
305
+ hooksChanged = true;
306
+ console.log(' Claude Code: installed ThumbGate status line');
307
+ }
308
+
309
+ if (hooksChanged) {
310
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
311
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
312
+ }
313
+
314
+ return mcpChanged || hooksChanged;
315
+ }
316
+
317
+ function setupCodex() {
318
+ const configPath = path.join(HOME, '.codex', 'config.toml');
319
+ const block = mcpSectionBlock(MCP_SERVER_NAME, 'home');
320
+ if (!fs.existsSync(configPath)) {
321
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
322
+ fs.writeFileSync(configPath, block);
323
+ console.log(' Codex: created ~/.codex/config.toml');
324
+ return true;
325
+ }
326
+ const content = fs.readFileSync(configPath, 'utf8');
327
+ const updated = upsertCodexServerConfig(content);
328
+ if (!updated.changed) return false;
329
+ fs.writeFileSync(configPath, updated.content);
330
+ console.log(' Codex: appended MCP server to ~/.codex/config.toml');
331
+ return true;
332
+ }
333
+
334
+ function setupGemini() {
335
+ const settingsPath = path.join(HOME, '.gemini', 'settings.json');
336
+ if (fs.existsSync(settingsPath)) {
337
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
338
+ settings.mcpServers = settings.mcpServers || {};
339
+ let changed = false;
340
+ const canonicalEntry = canonicalMcpEntry('home');
341
+
342
+ if (!mcpEntriesMatch(settings.mcpServers[MCP_SERVER_NAME], canonicalEntry)) {
343
+ settings.mcpServers[MCP_SERVER_NAME] = canonicalEntry;
344
+ changed = true;
345
+ }
346
+
347
+ for (const legacyName of LEGACY_MCP_SERVER_NAMES) {
348
+ if (legacyName === MCP_SERVER_NAME) continue;
349
+ if (Object.prototype.hasOwnProperty.call(settings.mcpServers, legacyName)) {
350
+ delete settings.mcpServers[legacyName];
351
+ changed = true;
352
+ }
353
+ }
354
+
355
+ if (!changed) return false;
356
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
357
+ console.log(' Gemini: updated ~/.gemini/settings.json');
358
+ return true;
359
+ }
360
+ // Fallback: project-level .gemini/settings.json
361
+ return mergeMcpJson(path.join(CWD, '.gemini', 'settings.json'), 'Gemini', 'project');
362
+ }
363
+
364
+ function setupAmp() {
365
+ const skillDir = path.join(CWD, '.amp', 'skills', 'rlhf-feedback');
366
+ const destPath = path.join(skillDir, 'SKILL.md');
367
+ if (fs.existsSync(destPath)) return false;
368
+ const srcPath = path.join(PKG_ROOT, 'plugins', 'amp-skill', 'SKILL.md');
369
+ if (!fs.existsSync(srcPath)) return false;
370
+ fs.mkdirSync(skillDir, { recursive: true });
371
+ fs.copyFileSync(srcPath, destPath);
372
+ console.log(' Amp: installed .amp/skills/rlhf-feedback/SKILL.md');
373
+ return true;
374
+ }
375
+
376
+ function setupCursor() {
377
+ return mergeMcpJson(path.join(CWD, '.cursor', 'mcp.json'), 'Cursor', 'project');
378
+ }
379
+
380
+ function init() {
381
+ const args = parseArgs(process.argv.slice(3));
382
+
383
+ // --wire-hooks only mode: skip scaffolding, just wire hooks
384
+ if (args['wire-hooks']) {
385
+ const { wireHooks, parseFlags: parseHookFlags } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
386
+ const hookResult = wireHooks({ agent: args.agent, dryRun: args['dry-run'] });
387
+ if (hookResult.error) {
388
+ console.error(hookResult.error);
389
+ process.exit(1);
390
+ }
391
+ if (!hookResult.changed) {
392
+ console.log(`Hooks already wired for ${hookResult.agent} at ${hookResult.settingsPath}`);
393
+ } else {
394
+ const prefix = args['dry-run'] ? '[DRY RUN] Would add' : 'Added';
395
+ console.log(`${prefix} hooks for ${hookResult.agent}:`);
396
+ for (const h of hookResult.added) {
397
+ console.log(` ${h.lifecycle}: ${h.command}`);
398
+ }
399
+ console.log(` Settings: ${hookResult.settingsPath}`);
400
+ }
401
+ return;
402
+ }
403
+
404
+ const rlhfDir = path.join(CWD, '.rlhf');
405
+ const configPath = path.join(rlhfDir, 'config.json');
406
+
407
+ if (!fs.existsSync(rlhfDir)) {
408
+ fs.mkdirSync(rlhfDir, { recursive: true });
409
+ console.log('Created .thumbgate/');
410
+ } else {
411
+ console.log('.thumbgate/ already exists — updating config');
412
+ }
413
+
414
+ let existingInstallId = null;
415
+ if (fs.existsSync(configPath)) {
416
+ try {
417
+ const existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
418
+ if (existingConfig && typeof existingConfig.installId === 'string' && existingConfig.installId.trim()) {
419
+ existingInstallId = existingConfig.installId.trim();
420
+ }
421
+ } catch (_) {
422
+ // Ignore invalid existing config and write a fresh one below.
423
+ }
424
+ }
425
+
426
+ const config = {
427
+ version: pkgVersion(),
428
+ apiUrl: process.env.THUMBGATE_API_URL || 'http://localhost:3000',
429
+ logPath: '.thumbgate/feedback-log.jsonl',
430
+ memoryPath: '.thumbgate/memory-log.jsonl',
431
+ installId: existingInstallId || crypto.randomUUID(),
432
+ createdAt: new Date().toISOString(),
433
+ };
434
+
435
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
436
+ console.log('Wrote .thumbgate/config.json');
437
+
438
+ // Always create .mcp.json (project-level MCP config used by Claude, Codex, Cursor)
439
+ mergeMcpJson(path.join(CWD, '.mcp.json'), 'MCP');
440
+
441
+ // Auto-detect and configure platform-specific locations
442
+ console.log('');
443
+ console.log('Detecting platforms...');
444
+ let configured = 0;
445
+
446
+ const platforms = [
447
+ { name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
448
+ { name: 'Gemini', detect: [() => whichExists('gemini'), () => fs.existsSync(path.join(HOME, '.gemini'))], setup: setupGemini },
449
+ { name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
450
+ { name: 'Cursor', detect: [() => fs.existsSync(path.join(HOME, '.cursor', 'mcp.json')), () => fs.existsSync(path.join(CWD, '.cursor'))], setup: setupCursor },
451
+ ];
452
+
453
+ for (const p of platforms) {
454
+ if (detectPlatform(p.name, p.detect)) {
455
+ const didSetup = p.setup();
456
+ if (didSetup) configured++;
457
+ else console.log(` ${p.name}: already configured`);
458
+ }
459
+ }
460
+
461
+ // ChatGPT — cannot be automated
462
+ const chatgptSpec = path.join(PKG_ROOT, 'adapters', 'chatgpt', 'openapi.yaml');
463
+ if (fs.existsSync(chatgptSpec)) {
464
+ const projectChatgptSpec = path.join(rlhfDir, 'chatgpt-openapi.yaml');
465
+ fs.copyFileSync(chatgptSpec, projectChatgptSpec);
466
+ console.log(` ChatGPT: import ${path.relative(CWD, projectChatgptSpec)} in GPT Builder > Actions`);
467
+ }
468
+
469
+ if (configured === 0) console.log(' All detected platforms already configured.');
470
+
471
+ // Auto-wire hooks if --agent flag is provided (or auto-detect)
472
+ if (args.agent || args['wire-hooks']) {
473
+ const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
474
+ const hookResult = wireHooks({ agent: args.agent, dryRun: args['dry-run'] });
475
+ if (hookResult.error) {
476
+ console.log(` Hook wiring: ${hookResult.error}`);
477
+ } else if (!hookResult.changed) {
478
+ console.log(` Hooks: already wired for ${hookResult.agent}`);
479
+ } else {
480
+ const prefix = args['dry-run'] ? '[DRY RUN] Would add' : 'Wired';
481
+ for (const h of hookResult.added) {
482
+ console.log(` ${prefix} ${h.lifecycle} hook: ${h.command}`);
483
+ }
484
+ }
485
+ }
486
+
487
+ // .gitignore
488
+ const gitignorePath = path.join(CWD, '.gitignore');
489
+ if (fs.existsSync(gitignorePath)) {
490
+ const gitignore = fs.readFileSync(gitignorePath, 'utf8');
491
+ const entries = ['.thumbgate/feedback-log.jsonl', '.thumbgate/memory-log.jsonl'];
492
+ const missing = entries.filter((e) => !gitignore.includes(e));
493
+ if (missing.length > 0) {
494
+ fs.appendFileSync(gitignorePath, '\n# ThumbGate local feedback data\n' + missing.join('\n') + '\n');
495
+ console.log('Updated .gitignore');
496
+ }
497
+ }
498
+
499
+ console.log('');
500
+ console.log(`thumbgate v${pkgVersion()} initialized.`);
501
+ console.log('Run: npx thumbgate help');
502
+ trackEvent('cli_init', { command: 'init' });
503
+ proNudge();
504
+ process.stderr.write(
505
+ '\n ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”\n' +
506
+ ' │ Free: unlimited šŸ‘šŸ‘Ž Ā· 5 searches Ā· 5 gates │\n' +
507
+ ' │ Pro: + dashboard + DPO export + full search │\n' +
508
+ ' │ $19/mo → npx thumbgate pro │\n' +
509
+ ' ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜\n\n'
510
+ );
511
+
512
+ try {
513
+ const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
514
+ appendFunnelEvent({
515
+ stage: 'acquisition',
516
+ event: 'cli_init_completed',
517
+ evidence: 'cli_init_completed',
518
+ installId: config.installId,
519
+ metadata: {
520
+ cwd: CWD,
521
+ version: config.version,
522
+ },
523
+ });
524
+ } catch (_) {
525
+ // Avoid failing init if telemetry write cannot be performed.
526
+ }
527
+ telemetryPing(config.installId);
528
+ }
529
+
530
+ function capture() {
531
+ const args = parseArgs(process.argv.slice(3));
532
+
533
+ // Delegate to the full engine
534
+ const { captureFeedback, analyzeFeedback, feedbackSummary, writePreventionRules } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
535
+ const { checkLimit } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
536
+
537
+ const { getUsage } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
538
+ const capLimit = checkLimit('capture_feedback');
539
+ if (!capLimit.allowed) {
540
+ limitNudge('capture_feedback');
541
+ process.exit(1);
542
+ }
543
+ trackEvent('cli_capture', { command: 'capture' });
544
+
545
+ if (args.stats) {
546
+ stats();
547
+ return;
548
+ }
549
+
550
+ if (args.summary) {
551
+ console.log(feedbackSummary(Number(args.recent || 20)));
552
+ return;
553
+ }
554
+
555
+ const signal = (args.feedback || '').toLowerCase();
556
+ const normalized = ['up', 'thumbsup', 'thumbs_up', 'positive'].some(v => signal.includes(v)) ? 'up'
557
+ : ['down', 'thumbsdown', 'thumbs_down', 'negative'].some(v => signal.includes(v)) ? 'down'
558
+ : signal;
559
+
560
+ if (normalized !== 'up' && normalized !== 'down') {
561
+ console.error('Missing or unrecognized --feedback=up|down');
562
+ process.exit(1);
563
+ }
564
+
565
+ const result = captureFeedback({
566
+ signal: normalized,
567
+ context: args.context || '',
568
+ whatWentWrong: args['what-went-wrong'],
569
+ whatToChange: args['what-to-change'],
570
+ whatWorked: args['what-worked'],
571
+ tags: args.tags,
572
+ });
573
+
574
+ if (result.accepted) {
575
+ const ev = result.feedbackEvent;
576
+ const mem = result.memoryRecord;
577
+ console.log(`\nFeedback Captured [${normalized.toUpperCase()}]`);
578
+ console.log('─'.repeat(50));
579
+ console.log(` Feedback ID : ${ev.id}`);
580
+ console.log(` Signal : ${ev.signal} (${ev.actionType})`);
581
+ console.log(` Memory ID : ${mem.id}`);
582
+ console.log(` Storage : JSONL log + LanceDB vector index`);
583
+ if (capLimit.used != null && capLimit.limit != null && capLimit.limit !== Infinity) {
584
+ const pct = Math.round((capLimit.used / capLimit.limit) * 100);
585
+ console.log(` Usage : ${capLimit.used}/${capLimit.limit} captures today (${pct}%)`);
586
+ if (capLimit.remaining <= 1) {
587
+ console.log(` āš ļø Last capture for today. Upgrade to Pro for unlimited.`);
588
+ }
589
+ }
590
+ console.log('');
591
+ proNudge();
592
+ } else {
593
+ console.log(`\nFeedback Recorded [${normalized.toUpperCase()}] — not promoted`);
594
+ console.log('─'.repeat(50));
595
+ console.log(` Reason : ${result.reason}\n`);
596
+ process.exit(2);
597
+ }
598
+ }
599
+
600
+ function stats() {
601
+ trackEvent('cli_stats', { command: 'stats' });
602
+ const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
603
+ const data = analyzeFeedback();
604
+
605
+ console.log('\nšŸ“Š ThumbGate Performance Metrics');
606
+ console.log('─'.repeat(50));
607
+ console.log(` Total Signals : ${data.total}`);
608
+ console.log(` Approval Rate : ${Math.round(data.approvalRate * 100)}%`);
609
+ console.log(` Recent Trend : ${Math.round(data.recentRate * 100)}%`);
610
+
611
+ // The Pitch: Revenue-at-Risk
612
+ const avgCostOfMistake = 2.50; // $2.50 per agent turn/fix
613
+ const revenueAtRisk = (data.totalNegative * avgCostOfMistake).toFixed(2);
614
+
615
+ if (data.totalNegative > 0) {
616
+ console.log('\nāš ļø REVENUE-AT-RISK ANALYSIS');
617
+ console.log(` Repeated Failures detected: ${data.totalNegative}`);
618
+ console.log(` Estimated Operational Loss: $${revenueAtRisk}`);
619
+ console.log(' Action Required: Run "npx thumbgate rules" to generate guardrails.');
620
+ console.log(' Strategic Recommendation: Upgrade to Context Gateway to sync these rules across your team.');
621
+ console.log(' Run: npx thumbgate pro');
622
+ } else {
623
+ console.log('\nāœ… System is currently high-reliability. No immediate revenue loss detected.');
624
+ }
625
+ proNudge();
626
+ }
627
+
628
+ function compact() {
629
+ const { compactMemories } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
630
+ const result = compactMemories();
631
+
632
+ console.log('\n🧹 Memory Compaction Complete');
633
+ console.log('─'.repeat(50));
634
+ console.log(` Before : ${result.before} memories`);
635
+ console.log(` After : ${result.after} memories`);
636
+ console.log(` Removed: ${result.removed} duplicates`);
637
+
638
+ if (result.removed > 0) {
639
+ console.log(`\nāœ… Eliminated ${Math.round((result.removed / result.before) * 100)}% noise.`);
640
+ } else {
641
+ console.log('\nāœ… No duplicates found — memory log is clean.');
642
+ }
643
+ }
644
+
645
+ function cfo() {
646
+ const args = parseArgs(process.argv.slice(3));
647
+ const { getOperationalBillingSummary } = require(path.join(PKG_ROOT, 'scripts', 'operational-summary'));
648
+ getOperationalBillingSummary({
649
+ window: args.window,
650
+ timeZone: args.timezone,
651
+ now: args.now,
652
+ })
653
+ .then(({ source, summary, fallbackReason }) => {
654
+ console.log(JSON.stringify({
655
+ source,
656
+ fallbackReason,
657
+ summary,
658
+ }, null, 2));
659
+ process.exit(0);
660
+ })
661
+ .catch((err) => {
662
+ console.error(err && err.message ? err.message : err);
663
+ process.exit(1);
664
+ });
665
+ }
666
+
667
+ function repairGithubMarketplace() {
668
+ const args = parseArgs(process.argv.slice(3));
669
+ const { repairGithubMarketplaceRevenueLedger } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
670
+ const result = repairGithubMarketplaceRevenueLedger({
671
+ write: Boolean(args.write),
672
+ });
673
+ console.log(JSON.stringify(result, null, 2));
674
+ process.exit(0);
675
+ }
676
+
677
+ function northStar() {
678
+ const args = parseArgs(process.argv.slice(3));
679
+ const { getOperationalDashboard } = require(path.join(PKG_ROOT, 'scripts', 'operational-dashboard'));
680
+
681
+ getOperationalDashboard({
682
+ window: args.window,
683
+ timeZone: args.timezone,
684
+ now: args.now,
685
+ })
686
+ .then(({ source, data, fallbackReason }) => {
687
+ const summary = data.analytics.northStar || {};
688
+ const revenue = data.analytics.revenue || {};
689
+
690
+ console.log('\nNorth Star');
691
+ console.log('─'.repeat(40));
692
+ console.log(`Metrics source : ${source}${fallbackReason ? ` (${fallbackReason})` : ''}`);
693
+ console.log(`Weekly proof-backed workflow runs : ${summary.weeklyActiveProofBackedWorkflowRuns || 0}`);
694
+ console.log(`Weekly teams on proof-backed runs : ${summary.weeklyTeamsRunningProofBackedWorkflows || 0}`);
695
+ console.log(`Reviewed workflow runs : ${summary.reviewedRuns || 0}`);
696
+ console.log(`Named pilot agreements : ${summary.namedPilotAgreements || 0}`);
697
+ console.log(`Paid team runs : ${summary.paidTeamRuns || 0}`);
698
+ console.log(`Paid orders : ${revenue.paidOrders || 0}`);
699
+ console.log(`Booked revenue : $${(Number(revenue.bookedRevenueCents || 0) / 100).toFixed(2)}`);
700
+ console.log(`Customer proof : ${summary.customerProofReached ? 'present' : 'missing'}`);
701
+ console.log(`North Star status : ${summary.northStarReached ? 'tracking' : 'not_started'}`);
702
+ if (summary.latestRun) {
703
+ console.log(`Latest proof-backed run : ${summary.latestRun.workflowId} @ ${summary.latestRun.timestamp}`);
704
+ }
705
+ console.log('');
706
+ process.exit(0);
707
+ })
708
+ .catch((err) => {
709
+ console.error(err && err.message ? err.message : err);
710
+ process.exit(1);
711
+ });
712
+ }
713
+
714
+ function pro() {
715
+ trackEvent('cli_pro_view', { command: 'pro' });
716
+ const args = parseArgs(process.argv.slice(3));
717
+ const {
718
+ resolveProKey,
719
+ saveLicense,
720
+ startLocalProDashboard,
721
+ } = require(path.join(PKG_ROOT, 'scripts', 'pro-local-dashboard'));
722
+
723
+ function printProInfo() {
724
+ const hostedUrl = 'https://thumbgate-production.up.railway.app';
725
+ const truthUrl = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md';
726
+ console.log('\nThumbGate Pro — Local Dashboard');
727
+ console.log('─'.repeat(50));
728
+ console.log('Self-serve offer today: Pro ($19/mo or $149/yr).');
729
+ console.log('Every licensed Pro user gets a personal local dashboard on localhost.');
730
+ console.log('\nWhat is available:');
731
+ console.log(' - Local Pro dashboard: your own browser dashboard for search, gates, and DPO export');
732
+ console.log(' - Optional hosted API key: shared lesson DB for teams and multi-agent workflows');
733
+ console.log(' - Commercial truth doc: source of truth for traction, pricing, and proof claims');
734
+ console.log('\nLinks:');
735
+ console.log(` Buy Pro : ${PRO_CHECKOUT_URL}`);
736
+ console.log(` Commercial truth: ${truthUrl}\n`);
737
+ console.log(' Launch dashboard: npx thumbgate pro');
738
+ console.log(' Activate + run : npx thumbgate pro --activate --key=YOUR_KEY');
739
+ console.log(' Install configs : npx thumbgate pro --upgrade');
740
+ console.log(' Legacy launcher : npx thumbgate-pro (separate package)');
741
+ console.log(' Pro repo : https://github.com/IgorGanapolsky/thumbgate-pro\n');
742
+ }
743
+
744
+ function launchDashboard(key, eventType) {
745
+ return startLocalProDashboard({ key })
746
+ .then(({ url }) => {
747
+ console.log(`\nšŸ‘šŸ‘Ž ThumbGate Pro dashboard: ${url}\n`);
748
+ appendLocalTelemetry({
749
+ eventType,
750
+ version: pkgVersion(),
751
+ timestamp: new Date().toISOString(),
752
+ });
753
+ })
754
+ .catch((err) => {
755
+ console.error(err && err.message ? err.message : err);
756
+ process.exit(1);
757
+ });
758
+ }
759
+
760
+ if (args.activate) {
761
+ const key = args.key || process.argv.slice(3).find((a) => !a.startsWith('--'));
762
+ if (!key) {
763
+ console.error('āŒ License key required. Usage: npx thumbgate pro --activate --key=YOUR_KEY');
764
+ console.error(' Your key was shown on the checkout success page after payment.');
765
+ process.exit(1);
766
+ }
767
+
768
+ // Validate key format (THUMBGATE_API_KEY prefix)
769
+ if (!key.startsWith('rlhf_') && !key.startsWith('tg_')) {
770
+ console.error('āŒ Invalid license key format. Keys start with "rlhf_" or "tg_".');
771
+ process.exit(1);
772
+ }
773
+
774
+ const license = {
775
+ key,
776
+ activatedAt: new Date().toISOString(),
777
+ version: pkgVersion(),
778
+ };
779
+
780
+ const licensePath = saveLicense(license.key, { version: license.version });
781
+ console.log('\nāœ… Pro license activated!');
782
+ console.log(` Key saved to: ${licensePath}`);
783
+ console.log(' Launching your personal local dashboard...\n');
784
+ return launchDashboard(license.key, 'pro_activate');
785
+ }
786
+
787
+ if (args.upgrade) {
788
+ const proDir = path.join(PKG_ROOT, 'pro');
789
+ const rlhfDir = path.join(CWD, '.rlhf');
790
+ if (!fs.existsSync(rlhfDir)) fs.mkdirSync(rlhfDir, { recursive: true });
791
+
792
+ const files = [
793
+ ['constraints-pro.json', '10 RLAIF constraints'],
794
+ ['prevention-rules-pro.md', 'curated production rules'],
795
+ ['thompson-presets.json', '4 sampling presets'],
796
+ ['reminders-pro.json', '8 reminder templates'],
797
+ ];
798
+
799
+ for (const [file] of files) {
800
+ fs.copyFileSync(path.join(proDir, file), path.join(rlhfDir, file));
801
+ }
802
+
803
+ console.log('\nāœ… Pro configs installed to .thumbgate/');
804
+ for (const [file, desc] of files) {
805
+ console.log(` - ${file} (${desc})`);
806
+ }
807
+ console.log('');
808
+
809
+ appendLocalTelemetry({ eventType: 'pro_upgrade', version: pkgVersion(), timestamp: new Date().toISOString() });
810
+ return;
811
+ }
812
+
813
+ if (args.info) {
814
+ printProInfo();
815
+ return;
816
+ }
817
+
818
+ const resolvedKey = resolveProKey();
819
+ if (resolvedKey && resolvedKey.key) {
820
+ return launchDashboard(resolvedKey.key, 'pro_dashboard_launch');
821
+ }
822
+
823
+ printProInfo();
824
+ }
825
+
826
+ function summary() {
827
+ const args = parseArgs(process.argv.slice(3));
828
+ const { feedbackSummary } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
829
+ console.log(feedbackSummary(Number(args.recent || 20)));
830
+ }
831
+
832
+ function lessons() {
833
+ trackEvent('cli_recall', { command: 'lessons' });
834
+ const args = parseArgs(process.argv.slice(3));
835
+ const { searchLessons, formatLessonSearchResults } = require(path.join(PKG_ROOT, 'scripts', 'lesson-search'));
836
+ const tags = String(args.tags || '')
837
+ .split(',')
838
+ .map((tag) => tag.trim())
839
+ .filter(Boolean);
840
+ const query = args.query || process.argv.slice(3).find((arg) => !arg.startsWith('--')) || '';
841
+ const result = searchLessons(query, {
842
+ limit: Number(args.limit || 10),
843
+ category: args.category,
844
+ tags,
845
+ });
846
+
847
+ if (args.json) {
848
+ console.log(JSON.stringify(result, null, 2));
849
+ return;
850
+ }
851
+
852
+ process.stdout.write(formatLessonSearchResults(result));
853
+ }
854
+
855
+ function modelFit() {
856
+ const { writeModelFitReport } = require(path.join(PKG_ROOT, 'scripts', 'local-model-profile'));
857
+ const { reportPath, report } = writeModelFitReport();
858
+ console.log(JSON.stringify({ reportPath, report }, null, 2));
859
+ }
860
+
861
+ function risk() {
862
+ const args = parseArgs(process.argv.slice(3));
863
+ const riskScorer = require(path.join(PKG_ROOT, 'scripts', 'risk-scorer'));
864
+
865
+ if (args.context || args.tags || args.skill || args.domain || args['rubric-scores'] || args.guardrails) {
866
+ const { inferDomain } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
867
+ const { buildRubricEvaluation } = require(path.join(PKG_ROOT, 'scripts', 'rubric-engine'));
868
+ const historyRows = riskScorer.readJSONL(riskScorer.sequencePathFor());
869
+ const tags = String(args.tags || '')
870
+ .split(',')
871
+ .map((tag) => tag.trim())
872
+ .filter(Boolean);
873
+
874
+ let rubric = null;
875
+ if (args['rubric-scores'] || args.guardrails) {
876
+ const evaluation = buildRubricEvaluation({
877
+ rubricScores: args['rubric-scores'],
878
+ guardrails: args.guardrails,
879
+ });
880
+ rubric = {
881
+ rubricId: evaluation.rubricId,
882
+ weightedScore: evaluation.weightedScore,
883
+ failingCriteria: evaluation.failingCriteria,
884
+ failingGuardrails: evaluation.failingGuardrails,
885
+ judgeDisagreements: evaluation.judgeDisagreements,
886
+ };
887
+ }
888
+
889
+ const candidate = riskScorer.buildRiskCandidate({
890
+ context: args.context || '',
891
+ tags,
892
+ skill: args.skill || null,
893
+ domain: args.domain || inferDomain(tags, args.context || ''),
894
+ rubric,
895
+ filePathCount: Number(args['file-count'] || 0),
896
+ errorType: args['error-type'] || null,
897
+ }, historyRows);
898
+ const model = riskScorer.loadRiskModel() || riskScorer.trainAndPersistRiskModel().model;
899
+ console.log(JSON.stringify({
900
+ prediction: riskScorer.predictRisk(model, candidate),
901
+ candidate,
902
+ }, null, 2));
903
+ return;
904
+ }
905
+
906
+ const { model, modelPath } = riskScorer.trainAndPersistRiskModel();
907
+ console.log(JSON.stringify({
908
+ modelPath,
909
+ metrics: model.metrics,
910
+ summary: riskScorer.getRiskSummary(),
911
+ }, null, 2));
912
+ }
913
+
914
+ function exportDpo() {
915
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
916
+ if (!isProTier(null)) {
917
+ process.stderr.write(
918
+ `\n šŸ”’ DPO Export requires Pro (${PRO_PRICE_LABEL}).\n` +
919
+ ` Your feedback would generate valuable training pairs.\n` +
920
+ ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
921
+ );
922
+ process.exit(1);
923
+ }
924
+ const extraArgs = process.argv.slice(3).join(' ');
925
+ try {
926
+ const output = execSync(
927
+ `node "${path.join(PKG_ROOT, 'scripts', 'export-dpo-pairs.js')}" --from-local ${extraArgs}`,
928
+ { encoding: 'utf8', stdio: 'pipe', cwd: CWD }
929
+ );
930
+ process.stdout.write(output);
931
+ } catch (err) {
932
+ process.stderr.write(err.stderr || err.stdout || err.message);
933
+ process.exit(err.status || 1);
934
+ }
935
+ }
936
+
937
+ function exportDatabricks() {
938
+ const { isProTier } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
939
+ if (!isProTier(null)) {
940
+ process.stderr.write(
941
+ `\n šŸ”’ Databricks Export requires Pro (${PRO_PRICE_LABEL}).\n` +
942
+ ` Export feedback logs + proof artifacts for analytics.\n` +
943
+ ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`
944
+ );
945
+ process.exit(1);
946
+ }
947
+ const extraArgs = process.argv.slice(3).join(' ');
948
+ try {
949
+ const output = execSync(
950
+ `node "${path.join(PKG_ROOT, 'scripts', 'export-databricks-bundle.js')}" ${extraArgs}`,
951
+ { encoding: 'utf8', stdio: 'pipe', cwd: CWD }
952
+ );
953
+ process.stdout.write(output);
954
+ } catch (err) {
955
+ process.stderr.write(err.stderr || err.stdout || err.message);
956
+ process.exit(err.status || 1);
957
+ }
958
+ }
959
+
960
+ function obsidianExport() {
961
+ const args = parseArgs(process.argv.slice(3));
962
+ const { exportAll } = require(path.join(PKG_ROOT, 'scripts', 'obsidian-export'));
963
+ const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
964
+
965
+ const vaultPath = args['vault-path'] || process.env.THUMBGATE_OBSIDIAN_VAULT_PATH || '';
966
+ const outputSubdir = args['output-dir'] || 'AI-Memories/rlhf';
967
+ let outputDir;
968
+ if (vaultPath) {
969
+ outputDir = path.join(vaultPath, outputSubdir);
970
+ } else {
971
+ outputDir = path.join(CWD, 'obsidian-export');
972
+ }
973
+
974
+ const { FEEDBACK_DIR } = getFeedbackPaths();
975
+ const gatesConfigPath = path.join(PKG_ROOT, 'config', 'gates', 'default.json');
976
+
977
+ const stats = exportAll({
978
+ feedbackDir: FEEDBACK_DIR,
979
+ outputDir,
980
+ gatesConfigPath,
981
+ includeIndex: true,
982
+ });
983
+
984
+ console.log(
985
+ `Exported ${stats.feedback} feedback, ${stats.memories} memories, ` +
986
+ `${stats.rules} rules, ${stats.gates} gates, ${stats.lessons} lessons`
987
+ );
988
+ if (stats.packs > 0) console.log(` + ${stats.packs} context packs`);
989
+ if (stats.errors.length > 0) {
990
+ console.error(` ${stats.errors.length} error(s) during export`);
991
+ }
992
+ console.log(`Output: ${outputDir}`);
993
+ process.exit(stats.errors.length > 0 ? 1 : 0);
994
+ }
995
+
996
+ function rules() {
997
+ const args = parseArgs(process.argv.slice(3));
998
+ const { writePreventionRules } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
999
+ const outPath = args.output || path.join(CWD, '.rlhf', 'prevention-rules.md');
1000
+ const result = writePreventionRules(outPath, Number(args.min || 2));
1001
+ console.log(`Wrote prevention rules to ${result.path}`);
1002
+ }
1003
+
1004
+ function selfHeal() {
1005
+ try {
1006
+ const output = execSync(
1007
+ `node "${path.join(PKG_ROOT, 'scripts', 'self-healing-check.js')}" && node "${path.join(PKG_ROOT, 'scripts', 'self-heal.js')}"`,
1008
+ { encoding: 'utf8', stdio: 'inherit', cwd: CWD }
1009
+ );
1010
+ } catch (err) {
1011
+ process.exit(err.status || 1);
1012
+ }
1013
+ }
1014
+
1015
+ function prove() {
1016
+ const args = parseArgs(process.argv.slice(3));
1017
+ const target = args.target || 'adapters';
1018
+ const script = path.join(PKG_ROOT, 'scripts', `prove-${target}.js`);
1019
+ if (!fs.existsSync(script)) {
1020
+ console.error(`Unknown proof target: ${target}`);
1021
+ console.error('Available: adapters, automation, attribution, lancedb, data-quality, intelligence, local-intelligence, loop-closure, training-export');
1022
+ process.exit(1);
1023
+ }
1024
+ try {
1025
+ execSync(`node "${script}"`, { encoding: 'utf8', stdio: 'inherit', cwd: CWD });
1026
+ } catch (err) {
1027
+ process.exit(err.status || 1);
1028
+ }
1029
+ }
1030
+
1031
+ function watchCmd() {
1032
+ const args = parseArgs(process.argv.slice(3));
1033
+ const { watch, once } = require(path.join(PKG_ROOT, 'scripts', 'jsonl-watcher'));
1034
+ const sourceFilter = args.source || undefined;
1035
+ if (args.once) {
1036
+ once(sourceFilter);
1037
+ } else {
1038
+ watch(sourceFilter);
1039
+ }
1040
+ }
1041
+
1042
+ function status() {
1043
+ const statusDashboard = require(path.join(PKG_ROOT, 'scripts', 'status-dashboard'));
1044
+ const { getFeedbackPaths } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
1045
+ const { FEEDBACK_DIR } = getFeedbackPaths();
1046
+ const data = statusDashboard.generateStatus(FEEDBACK_DIR);
1047
+ // printDashboard writes directly to stdout when run as main;
1048
+ // for CLI we call the same renderer
1049
+ statusDashboard.printDashboard
1050
+ ? statusDashboard.printDashboard(data)
1051
+ : console.log(JSON.stringify(data, null, 2));
1052
+ }
1053
+
1054
+ function funnel() {
1055
+ const { generateFunnelReport } = require(path.join(PKG_ROOT, 'scripts', 'funnel-analytics'));
1056
+ generateFunnelReport();
1057
+ }
1058
+
1059
+ function pulse() {
1060
+ const { showPulse } = require(path.join(PKG_ROOT, 'scripts', 'pulse'));
1061
+ showPulse().catch((err) => {
1062
+ console.error(err && err.message ? err.message : err);
1063
+ process.exit(1);
1064
+ }).then(() => {
1065
+ process.exit(0);
1066
+ });
1067
+ }
1068
+
1069
+ function dispatchBrief() {
1070
+ const args = parseArgs(process.argv.slice(3));
1071
+ const {
1072
+ getDispatchBrief,
1073
+ formatDispatchBrief,
1074
+ } = require(path.join(PKG_ROOT, 'scripts', 'dispatch-brief'));
1075
+
1076
+ getDispatchBrief({
1077
+ window: args.window,
1078
+ timeZone: args.timezone,
1079
+ now: args.now,
1080
+ profile: args.profile || 'dispatch',
1081
+ })
1082
+ .then((brief) => {
1083
+ if (args.json) {
1084
+ console.log(JSON.stringify(brief, null, 2));
1085
+ } else {
1086
+ process.stdout.write(formatDispatchBrief(brief));
1087
+ }
1088
+ process.exit(0);
1089
+ })
1090
+ .catch((err) => {
1091
+ console.error(err && err.message ? err.message : err);
1092
+ process.exit(1);
1093
+ });
1094
+ }
1095
+
1096
+ function gateStats() {
1097
+ const { calculateStats, formatStats } = require(path.join(PKG_ROOT, 'scripts', 'gate-stats'));
1098
+ const stats = calculateStats();
1099
+ console.log('\n' + formatStats(stats) + '\n');
1100
+ }
1101
+
1102
+ function optimize() {
1103
+ const { optimize: doOptimize } = require(path.join(PKG_ROOT, 'scripts', 'optimize-context'));
1104
+ doOptimize();
1105
+ }
1106
+
1107
+ function serve() {
1108
+ // Start MCP server over stdio
1109
+ const mcpServer = path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
1110
+ const { startStdioServer } = require(mcpServer);
1111
+ startStdioServer();
1112
+ // Start watcher as a background daemon alongside MCP server
1113
+ try {
1114
+ const { watch } = require(path.join(PKG_ROOT, 'scripts', 'jsonl-watcher'));
1115
+ watch();
1116
+ } catch (_) { /* watcher is non-critical */ }
1117
+ }
1118
+
1119
+ function install() {
1120
+ console.log('Installing ThumbGate as a global MCP skill...');
1121
+ const results = [
1122
+ setupClaude(),
1123
+ setupCodex(),
1124
+ setupGemini(),
1125
+ setupCursor(),
1126
+ setupAmp()
1127
+ ];
1128
+ const success = results.some(r => r === true);
1129
+ if (success) {
1130
+ console.log('\nSuccess! ThumbGate is now available to your agents.');
1131
+ console.log('Try asking your agent: "Capture positive feedback for this task"');
1132
+ } else {
1133
+ console.log('\nThumbGate is already configured.');
1134
+ }
1135
+ }
1136
+
1137
+ function installMcp() {
1138
+ const { installMcp: doInstall, parseFlags } = require(path.join(PKG_ROOT, 'scripts', 'install-mcp'));
1139
+ const flags = parseFlags(process.argv.slice(3));
1140
+ doInstall(flags);
1141
+ }
1142
+
1143
+ function dashboard() {
1144
+ const args = parseArgs(process.argv.slice(3));
1145
+ const { printDashboard } = require(path.join(PKG_ROOT, 'scripts', 'dashboard'));
1146
+ const { getOperationalDashboard } = require(path.join(PKG_ROOT, 'scripts', 'operational-dashboard'));
1147
+
1148
+ getOperationalDashboard({
1149
+ window: args.window,
1150
+ timeZone: args.timezone,
1151
+ now: args.now,
1152
+ })
1153
+ .then(({ data }) => {
1154
+ printDashboard(data);
1155
+ process.exit(0);
1156
+ })
1157
+ .catch((err) => {
1158
+ console.error(err && err.message ? err.message : err);
1159
+ process.exit(1);
1160
+ });
1161
+ }
1162
+
1163
+ function gateStats() {
1164
+ const { calculateStats, formatStats } = require(path.join(PKG_ROOT, 'scripts', 'gate-stats'));
1165
+ const stats = calculateStats();
1166
+ console.log('\n' + formatStats(stats) + '\n');
1167
+ }
1168
+
1169
+ function startApi() {
1170
+ const serverPath = path.join(PKG_ROOT, 'src', 'api', 'server.js');
1171
+ try {
1172
+ execSync(`node "${serverPath}"`, { stdio: 'inherit', cwd: CWD });
1173
+ } catch (err) {
1174
+ process.exit(err.status || 1);
1175
+ }
1176
+ }
1177
+
1178
+ function help() {
1179
+ const v = pkgVersion();
1180
+ console.log(`thumbgate v${v}`);
1181
+ console.log('');
1182
+ console.log('Commands:');
1183
+ console.log(' init Scaffold .thumbgate/ config + MCP server in current project');
1184
+ console.log(' --agent=NAME Wire PreToolUse hooks for agent (claude-code|codex|gemini)');
1185
+ console.log(' --wire-hooks Wire hooks only (auto-detect agent, skip scaffolding)');
1186
+ console.log(' --dry-run Preview hook changes without writing');
1187
+ console.log(' install-mcp Install ThumbGate MCP server into Claude Code settings (--project for local)');
1188
+ console.log(' serve Start MCP server (stdio) — for claude/codex/gemini mcp add');
1189
+ console.log(' capture [flags] Capture feedback (--feedback=up|down --context="..." --tags="...")');
1190
+ console.log(' stats Show feedback analytics + Revenue-at-Risk');
1191
+ console.log(' cfo Show hosted billing summary when configured, else local fallback JSON');
1192
+ console.log(' repair-github-marketplace Dry-run or apply legacy GitHub Marketplace amount repairs (--write)');
1193
+ console.log(' north-star Show proof-backed workflow-run progress toward the North Star');
1194
+ console.log(' summary Human-readable feedback summary');
1195
+ console.log(' lessons [flags] Search promoted lessons and show linked corrective actions');
1196
+ console.log(' model-fit Detect the current local embedding profile and write evidence report');
1197
+ console.log(' risk [flags] Train or query the boosted local risk scorer');
1198
+ console.log(' doctor Audit runtime isolation, bootstrap context, and permission tier');
1199
+ console.log(' dispatch Print a Dispatch-safe remote ops brief for phone-driven review sessions');
1200
+ console.log(' export-dpo Export DPO training pairs (prompt/chosen/rejected JSONL)');
1201
+ console.log(' export-databricks Export feedback logs + proof artifacts as a Databricks-ready analytics bundle');
1202
+ console.log(' obsidian-export Export all feedback data as interlinked Obsidian markdown notes');
1203
+ console.log(' --vault-path=PATH Obsidian vault path (or set THUMBGATE_OBSIDIAN_VAULT_PATH)');
1204
+ console.log(' --output-dir=DIR Output subdirectory (default: AI-Memories/rlhf)');
1205
+ console.log(' rules Generate prevention rules from repeated failures');
1206
+ console.log(' optimize [PRO] Prune CLAUDE.md and migrate manual rules to Pre-Action Gates');
1207
+ console.log(' force-gate <PATTERN> Immediately create a blocking gate from a pattern');
1208
+ console.log(' self-heal Run self-healing check and auto-fix');
1209
+ console.log(' activate <KEY> Activate a Pro license key (from Stripe checkout)');
1210
+ console.log(' pro Show Pro plan ($19/mo) + hosted pilot info');
1211
+ console.log(' --upgrade Install Pro configs into .thumbgate/');
1212
+ console.log(' prove [--target=X] Run proof harness (adapters|automation|attribution|lancedb|local-intelligence|...)');
1213
+ console.log(' watch [flags] Watch .thumbgate/ for external signals and ingest through pipeline (--once, --source=X)');
1214
+ console.log(' status Show feedback tracking dashboard — approval trend + failure domains');
1215
+ console.log(' dashboard Full ThumbGate dashboard — approval rate, gate stats, prevention impact');
1216
+ console.log(' funnel Show marketing & revenue conversion funnel analytics');
1217
+ console.log(' pulse Show real-time GTM velocity and Mission Control summary');
1218
+ console.log(' dispatch Dispatch-safe brief — metrics, gates, and read-only prompt templates');
1219
+ console.log(' gate-stats Show gate statistics — active gates, blocks, warns, time saved');
1220
+ console.log(' analytics Unified ThumbGate analytics snapshot (npm, GitHub, landing page)');
1221
+ console.log(' start-api Start the Memory Gateway HTTPS API server');
1222
+ console.log(' help Show this help message');
1223
+ console.log('');
1224
+ console.log('Examples:');
1225
+ console.log(' npx thumbgate init');
1226
+ console.log(' npx thumbgate stats');
1227
+ console.log(' npx thumbgate cfo');
1228
+ console.log(' npx thumbgate repair-github-marketplace --write');
1229
+ console.log(' npx thumbgate lessons --query="verification" --limit=5');
1230
+ console.log(' npx thumbgate model-fit');
1231
+ console.log(' npx thumbgate risk');
1232
+ console.log(' npx thumbgate pro');
1233
+ proNudge();
1234
+ }
1235
+
1236
+ if (COMMAND === 'daemon' || COMMAND === 'serve-daemon') {
1237
+ const subCmd = process.argv[3] || 'status';
1238
+ const { manageDaemon } = require(path.join(PKG_ROOT, 'scripts', 'daemon-manager'));
1239
+ manageDaemon(subCmd);
1240
+ process.exit(0);
1241
+ }
1242
+
1243
+ switch (COMMAND) {
1244
+ case 'init':
1245
+ init();
1246
+ upgradeNudge();
1247
+ break;
1248
+ case 'install':
1249
+ install();
1250
+ break;
1251
+ case 'install-mcp':
1252
+ installMcp();
1253
+ break;
1254
+ case 'serve':
1255
+ case 'mcp':
1256
+ serve();
1257
+ break;
1258
+ case 'capture':
1259
+ case 'feedback':
1260
+ capture();
1261
+ upgradeNudge();
1262
+ break;
1263
+ case 'stats':
1264
+ stats();
1265
+ upgradeNudge();
1266
+ break;
1267
+ case 'cfo':
1268
+ case 'revenue':
1269
+ cfo();
1270
+ break;
1271
+ case 'repair-github-marketplace':
1272
+ repairGithubMarketplace();
1273
+ break;
1274
+ case 'north-star':
1275
+ northStar();
1276
+ break;
1277
+ case 'summary':
1278
+ summary();
1279
+ break;
1280
+ case 'lessons':
1281
+ case 'search-lessons':
1282
+ lessons();
1283
+ break;
1284
+ case 'lesson-health':
1285
+ case 'stale': {
1286
+ const { initDB } = require(path.join(PKG_ROOT, 'scripts', 'lesson-db'));
1287
+ const { stalenessReport, autoArchive } = require(path.join(PKG_ROOT, 'scripts', 'lesson-rotation'));
1288
+ const staleArgs = parseArgs(process.argv.slice(3));
1289
+ const db = initDB();
1290
+ if (staleArgs.archive) {
1291
+ const result = autoArchive(db);
1292
+ console.log(`\nāœ… Auto-archived ${result.archived} stale lessons (>90 days inactive)\n`);
1293
+ } else {
1294
+ const report = stalenessReport(db);
1295
+ console.log(`\nLesson Health Report`);
1296
+ console.log('─'.repeat(50));
1297
+ console.log(` Total active : ${report.total}`);
1298
+ console.log(` Healthy : ${report.healthy}`);
1299
+ console.log(` Stale (>60d) : ${report.stale.length}`);
1300
+ console.log(` Archivable : ${report.archivable.length}`);
1301
+ if (report.stale.length > 0) {
1302
+ console.log(`\n Stale lessons:`);
1303
+ for (const l of report.stale.slice(0, 10)) {
1304
+ console.log(` ${l.id.slice(0, 8)}... ${l.daysSinceActive}d inactive, ${l.triggerCount} triggers — ${l.context}`);
1305
+ }
1306
+ if (report.stale.length > 10) console.log(` ... and ${report.stale.length - 10} more`);
1307
+ }
1308
+ if (report.archivable.length > 0) {
1309
+ console.log(`\n Run with --archive to auto-archive ${report.archivable.length} lessons >90 days inactive.`);
1310
+ }
1311
+ console.log('');
1312
+ }
1313
+ db.close();
1314
+ break;
1315
+ }
1316
+ case 'lesson-review': {
1317
+ const { isProTier: isProForReview } = require(path.join(PKG_ROOT, 'scripts', 'rate-limiter'));
1318
+ if (!isProForReview(null)) {
1319
+ process.stderr.write(`\n šŸ”’ Lesson Review requires Pro (${PRO_PRICE_LABEL}).\n` +
1320
+ ` Review stale lessons and decide what to keep, archive, or promote.\n` +
1321
+ ` Upgrade: ${PRO_CHECKOUT_URL}\n\n`);
1322
+ process.exit(1);
1323
+ }
1324
+ const { initDB: initDBReview } = require(path.join(PKG_ROOT, 'scripts', 'lesson-db'));
1325
+ const { findStaleLessons, restoreLesson, autoArchive: autoArchiveReview } = require(path.join(PKG_ROOT, 'scripts', 'lesson-rotation'));
1326
+ const reviewDb = initDBReview();
1327
+ const stale = findStaleLessons(reviewDb);
1328
+ if (stale.length === 0) {
1329
+ console.log('\nāœ… No stale lessons. All lessons are active and healthy.\n');
1330
+ } else {
1331
+ console.log(`\nšŸ“‹ Lesson Review — ${stale.length} stale lessons\n`);
1332
+ for (const l of stale) {
1333
+ const ageDays = Math.round((Date.now() - new Date(l.last_triggered || l.timestamp).getTime()) / 86400000);
1334
+ console.log(` [${l.importance || 'medium'}] ${l.id.slice(0, 12)} ${ageDays}d inactive`);
1335
+ console.log(` ${(l.context || l.whatToChange || '').slice(0, 100)}`);
1336
+ console.log('');
1337
+ }
1338
+ console.log(` Run "npx thumbgate stale --archive" to archive all ${stale.length} stale lessons.\n`);
1339
+ }
1340
+ reviewDb.close();
1341
+ break;
1342
+ }
1343
+ case 'model-fit':
1344
+ modelFit();
1345
+ break;
1346
+ case 'risk':
1347
+ risk();
1348
+ break;
1349
+ case 'doctor': {
1350
+ const {
1351
+ generateAgentReadinessReport,
1352
+ reportToText,
1353
+ } = require(path.join(PKG_ROOT, 'scripts', 'agent-readiness'));
1354
+ const args = parseArgs(process.argv.slice(3));
1355
+ const report = generateAgentReadinessReport({ projectRoot: CWD });
1356
+ if (args.json) {
1357
+ console.log(JSON.stringify(report, null, 2));
1358
+ } else {
1359
+ process.stdout.write(reportToText(report));
1360
+ }
1361
+ process.exit(report.overallStatus === 'ready' ? 0 : 1);
1362
+ break;
1363
+ }
1364
+ case 'export-dpo':
1365
+ case 'dpo':
1366
+ exportDpo();
1367
+ break;
1368
+ case 'export-databricks':
1369
+ case 'databricks':
1370
+ exportDatabricks();
1371
+ break;
1372
+ case 'obsidian-export':
1373
+ obsidianExport();
1374
+ break;
1375
+ case 'rules':
1376
+ rules();
1377
+ break;
1378
+ case 'optimize':
1379
+ optimize();
1380
+ break;
1381
+ case 'force-gate': {
1382
+ const context = process.argv.slice(3).find(a => !a.startsWith('--'));
1383
+ if (!context) {
1384
+ console.error('Error: context string is required for force-gate');
1385
+ process.exit(1);
1386
+ }
1387
+ const { forcePromote } = require('../scripts/auto-promote-gates');
1388
+ const result = forcePromote(context, 'block');
1389
+ console.log(`āœ… Forced block gate created: ${result.gateId}`);
1390
+ console.log(`Total auto-promoted gates: ${result.totalGates}`);
1391
+ break;
1392
+ }
1393
+ case 'self-heal':
1394
+ selfHeal();
1395
+ break;
1396
+ case 'pro':
1397
+ pro();
1398
+ break;
1399
+ case 'activate':
1400
+ // Top-level alias: npx thumbgate activate <key>
1401
+ process.argv.splice(3, 0, '--activate');
1402
+ pro();
1403
+ break;
1404
+ case 'prove':
1405
+ prove();
1406
+ break;
1407
+ case 'watch':
1408
+ watchCmd();
1409
+ break;
1410
+ case 'status':
1411
+ status();
1412
+ break;
1413
+ case 'funnel':
1414
+ funnel();
1415
+ break;
1416
+ case 'pulse':
1417
+ pulse();
1418
+ break;
1419
+ case 'dispatch':
1420
+ case 'dispatch-brief':
1421
+ dispatchBrief();
1422
+ break;
1423
+ case 'gate-stats':
1424
+ gateStats();
1425
+ break;
1426
+ case 'dashboard':
1427
+ dashboard();
1428
+ break;
1429
+ case 'analytics': {
1430
+ const { run: runAnalytics } = require(path.join(PKG_ROOT, 'scripts', 'analytics-report'));
1431
+ runAnalytics();
1432
+ break;
1433
+ }
1434
+ case 'start-api':
1435
+ startApi();
1436
+ break;
1437
+ case 'help':
1438
+ case '--help':
1439
+ case '-h':
1440
+ help();
1441
+ break;
1442
+ case 'compact':
1443
+ compact();
1444
+ break;
1445
+ case 'checkin': {
1446
+ // User check-in command — asks how it's going after install
1447
+ const rlhfDir = path.join(CWD, '.rlhf');
1448
+ const configPath = path.join(rlhfDir, 'config.json');
1449
+ let installAge = 'unknown';
1450
+ if (fs.existsSync(configPath)) {
1451
+ try {
1452
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1453
+ if (config.installedAt) {
1454
+ const days = Math.floor((Date.now() - new Date(config.installedAt).getTime()) / 86400000);
1455
+ installAge = `${days} day${days !== 1 ? 's' : ''}`;
1456
+ }
1457
+ } catch { /* ignore */ }
1458
+ }
1459
+ console.log(`\nšŸ”” thumbgate check-in (installed ${installAge} ago)\n`);
1460
+ console.log('Quick questions to help improve this tool:\n');
1461
+ console.log('1. Is the gate engine catching real mistakes for you? (y/n/haven\'t tried)');
1462
+ console.log('2. What failure pattern do you wish it caught but doesn\'t?');
1463
+ console.log('3. Anything confusing or broken?\n');
1464
+ console.log('Reply to any of these at: https://github.com/IgorGanapolsky/ThumbGate/discussions');
1465
+ console.log('Or email: iganapolsky@gmail.com\n');
1466
+
1467
+ // Log the check-in event
1468
+ const checkinLog = path.join(rlhfDir, 'checkin-log.jsonl');
1469
+ if (fs.existsSync(rlhfDir)) {
1470
+ const event = { event: 'checkin_shown', at: new Date().toISOString(), installAge };
1471
+ fs.appendFileSync(checkinLog, JSON.stringify(event) + '\n');
1472
+ }
1473
+ break;
1474
+ }
1475
+ default:
1476
+ if (COMMAND) {
1477
+ console.error(`Unknown command: ${COMMAND}`);
1478
+ console.error('Run: npx thumbgate help');
1479
+ process.exit(1);
1480
+ } else {
1481
+ help();
1482
+ }
1483
+ }