thumbgate 0.9.10

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 (364) 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/thumbgate-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 +1484 -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 +283 -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 +1014 -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-312.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 +299 -0
  117. package/scripts/auto-wire-hooks.js +312 -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 +97 -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 +263 -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 +209 -0
  152. package/scripts/ephemeral-agent-store.js +219 -0
  153. package/scripts/eval-harness.js +56 -0
  154. package/scripts/evolution-state.js +241 -0
  155. package/scripts/experiment-tracker.js +267 -0
  156. package/scripts/export-databricks-bundle.js +242 -0
  157. package/scripts/export-dpo-pairs.js +345 -0
  158. package/scripts/export-kto-pairs.js +310 -0
  159. package/scripts/export-training.js +448 -0
  160. package/scripts/failure-diagnostics.js +558 -0
  161. package/scripts/feedback-attribution.js +313 -0
  162. package/scripts/feedback-fallback.js +111 -0
  163. package/scripts/feedback-history-distiller.js +391 -0
  164. package/scripts/feedback-inbox-read.js +162 -0
  165. package/scripts/feedback-loop.js +1887 -0
  166. package/scripts/feedback-paths.js +145 -0
  167. package/scripts/feedback-quality.js +139 -0
  168. package/scripts/feedback-root-consolidator.js +238 -0
  169. package/scripts/feedback-schema.js +426 -0
  170. package/scripts/feedback-session.js +286 -0
  171. package/scripts/feedback-to-memory.js +185 -0
  172. package/scripts/feedback-to-rules.js +163 -0
  173. package/scripts/filesystem-search.js +404 -0
  174. package/scripts/funnel-analytics.js +35 -0
  175. package/scripts/gate-satisfy.js +42 -0
  176. package/scripts/gate-stats.js +116 -0
  177. package/scripts/gate-templates.js +70 -0
  178. package/scripts/gates-engine.js +816 -0
  179. package/scripts/generate-paperbanana-diagrams.sh +99 -0
  180. package/scripts/generate-pretool-hook.sh +40 -0
  181. package/scripts/github-about.js +350 -0
  182. package/scripts/github-outreach.js +65 -0
  183. package/scripts/gtm-revenue-loop.js +520 -0
  184. package/scripts/hallucination-detector.js +226 -0
  185. package/scripts/hf-papers.js +317 -0
  186. package/scripts/history-distiller.js +200 -0
  187. package/scripts/hook-auto-capture.sh +95 -0
  188. package/scripts/hook-stop-pr-thread-check.sh +68 -0
  189. package/scripts/hook-stop-self-score.sh +51 -0
  190. package/scripts/hook-stop-verify-deploy.sh +31 -0
  191. package/scripts/hook-thumbgate-cache-updater.js +48 -0
  192. package/scripts/hook-verify-before-done.sh +20 -0
  193. package/scripts/hosted-config.js +170 -0
  194. package/scripts/hybrid-feedback-context.js +676 -0
  195. package/scripts/install-mcp.js +159 -0
  196. package/scripts/intent-router.js +392 -0
  197. package/scripts/internal-agent-bootstrap.js +490 -0
  198. package/scripts/jsonl-watcher.js +155 -0
  199. package/scripts/lesson-db.js +613 -0
  200. package/scripts/lesson-inference.js +315 -0
  201. package/scripts/lesson-retrieval.js +95 -0
  202. package/scripts/lesson-rotation.js +137 -0
  203. package/scripts/lesson-search.js +644 -0
  204. package/scripts/lesson-synthesis.js +196 -0
  205. package/scripts/license.js +50 -0
  206. package/scripts/local-model-profile.js +383 -0
  207. package/scripts/markdown-escape.js +12 -0
  208. package/scripts/marketing-experiment.js +671 -0
  209. package/scripts/mcp-config.js +149 -0
  210. package/scripts/mcp-policy.js +99 -0
  211. package/scripts/memalign-recall.js +111 -0
  212. package/scripts/memory-firewall.js +222 -0
  213. package/scripts/memory-migration.js +296 -0
  214. package/scripts/meta-policy.js +194 -0
  215. package/scripts/metered-billing.js +16 -0
  216. package/scripts/model-tier-router.js +301 -0
  217. package/scripts/money-watcher.js +71 -0
  218. package/scripts/multi-hop-recall.js +240 -0
  219. package/scripts/natural-language-harness.js +330 -0
  220. package/scripts/obsidian-export.js +712 -0
  221. package/scripts/operational-dashboard.js +103 -0
  222. package/scripts/operational-summary.js +93 -0
  223. package/scripts/optimize-context.js +17 -0
  224. package/scripts/org-dashboard.js +201 -0
  225. package/scripts/partner-orchestration.js +146 -0
  226. package/scripts/per-step-scoring.js +165 -0
  227. package/scripts/perplexity-marketing.js +466 -0
  228. package/scripts/pii-scanner.js +153 -0
  229. package/scripts/plan-gate.js +154 -0
  230. package/scripts/post-everywhere.js +308 -0
  231. package/scripts/post-to-x-retry.sh +22 -0
  232. package/scripts/post-to-x.js +369 -0
  233. package/scripts/pr-manager.js +236 -0
  234. package/scripts/predictive-insights.js +356 -0
  235. package/scripts/principle-extractor.js +162 -0
  236. package/scripts/pro-features.js +40 -0
  237. package/scripts/pro-local-dashboard.js +174 -0
  238. package/scripts/problem-detail.js +53 -0
  239. package/scripts/product-feedback.js +134 -0
  240. package/scripts/profile-router.js +245 -0
  241. package/scripts/prompt-dlp.js +221 -0
  242. package/scripts/prompt-guard.js +83 -0
  243. package/scripts/prove-adapters.js +863 -0
  244. package/scripts/prove-attribution.js +365 -0
  245. package/scripts/prove-automation.js +653 -0
  246. package/scripts/prove-autoresearch.js +304 -0
  247. package/scripts/prove-claim-verification.js +277 -0
  248. package/scripts/prove-cloudflare-sandbox.js +163 -0
  249. package/scripts/prove-data-pipeline.js +410 -0
  250. package/scripts/prove-data-quality.js +227 -0
  251. package/scripts/prove-evolution.js +352 -0
  252. package/scripts/prove-harnesses.js +287 -0
  253. package/scripts/prove-intelligence.js +259 -0
  254. package/scripts/prove-lancedb.js +371 -0
  255. package/scripts/prove-local-intelligence.js +342 -0
  256. package/scripts/prove-loop-closure.js +263 -0
  257. package/scripts/prove-predictive-insights.js +357 -0
  258. package/scripts/prove-runtime.js +350 -0
  259. package/scripts/prove-seo-gsd.js +234 -0
  260. package/scripts/prove-settings.js +279 -0
  261. package/scripts/prove-subway-upgrades.js +277 -0
  262. package/scripts/prove-tessl.js +229 -0
  263. package/scripts/prove-training-export.js +327 -0
  264. package/scripts/prove-workflow-contract.js +116 -0
  265. package/scripts/prove-xmemory.js +332 -0
  266. package/scripts/publish-decision.js +133 -0
  267. package/scripts/pulse.js +80 -0
  268. package/scripts/rate-limiter.js +125 -0
  269. package/scripts/reddit-dm-outreach.js +182 -0
  270. package/scripts/reddit-monitor-cron.sh +26 -0
  271. package/scripts/reflector-agent.js +221 -0
  272. package/scripts/reminder-engine.js +132 -0
  273. package/scripts/revenue-status.js +472 -0
  274. package/scripts/risk-scorer.js +458 -0
  275. package/scripts/rlaif-self-audit.js +129 -0
  276. package/scripts/rubric-engine.js +230 -0
  277. package/scripts/schedule-manager.js +251 -0
  278. package/scripts/secret-scanner.js +414 -0
  279. package/scripts/self-heal.js +147 -0
  280. package/scripts/self-healing-check.js +188 -0
  281. package/scripts/semantic-layer.js +98 -0
  282. package/scripts/seo-gsd.js +1153 -0
  283. package/scripts/settings-hierarchy.js +214 -0
  284. package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
  285. package/scripts/skill-exporter.js +262 -0
  286. package/scripts/skill-generator.js +446 -0
  287. package/scripts/skill-materializer.js +134 -0
  288. package/scripts/skill-packs.js +136 -0
  289. package/scripts/skill-proposer.js +99 -0
  290. package/scripts/skill-quality-tracker.js +284 -0
  291. package/scripts/slo-alert-engine.js +14 -0
  292. package/scripts/slow-loop.js +72 -0
  293. package/scripts/social-analytics/db/schema.sql +32 -0
  294. package/scripts/social-analytics/digest.js +256 -0
  295. package/scripts/social-analytics/generate-instagram-card.js +97 -0
  296. package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
  297. package/scripts/social-analytics/mcp-server.js +289 -0
  298. package/scripts/social-analytics/normalizer.js +580 -0
  299. package/scripts/social-analytics/notify.js +162 -0
  300. package/scripts/social-analytics/poll-all.js +107 -0
  301. package/scripts/social-analytics/pollers/github.js +195 -0
  302. package/scripts/social-analytics/pollers/instagram.js +253 -0
  303. package/scripts/social-analytics/pollers/linkedin.js +330 -0
  304. package/scripts/social-analytics/pollers/plausible.js +247 -0
  305. package/scripts/social-analytics/pollers/reddit.js +306 -0
  306. package/scripts/social-analytics/pollers/threads.js +233 -0
  307. package/scripts/social-analytics/pollers/tiktok.js +203 -0
  308. package/scripts/social-analytics/pollers/x.js +227 -0
  309. package/scripts/social-analytics/pollers/youtube.js +304 -0
  310. package/scripts/social-analytics/pollers/zernio.js +180 -0
  311. package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
  312. package/scripts/social-analytics/publishers/devto.js +122 -0
  313. package/scripts/social-analytics/publishers/instagram.js +317 -0
  314. package/scripts/social-analytics/publishers/linkedin.js +294 -0
  315. package/scripts/social-analytics/publishers/reddit.js +390 -0
  316. package/scripts/social-analytics/publishers/threads.js +275 -0
  317. package/scripts/social-analytics/publishers/tiktok.js +217 -0
  318. package/scripts/social-analytics/publishers/x.js +259 -0
  319. package/scripts/social-analytics/publishers/youtube.js +223 -0
  320. package/scripts/social-analytics/publishers/zernio.js +209 -0
  321. package/scripts/social-analytics/run-digest.js +34 -0
  322. package/scripts/social-analytics/store.js +257 -0
  323. package/scripts/social-analytics/utm.js +143 -0
  324. package/scripts/social-pipeline.js +2628 -0
  325. package/scripts/social-quality-gate.js +18 -0
  326. package/scripts/social-reply-monitor.js +445 -0
  327. package/scripts/status-dashboard.js +155 -0
  328. package/scripts/statusline-lesson.js +16 -0
  329. package/scripts/statusline-tower.js +8 -0
  330. package/scripts/statusline.sh +116 -0
  331. package/scripts/stripe-live-status.js +115 -0
  332. package/scripts/subagent-profiles.js +79 -0
  333. package/scripts/sync-gh-secrets-from-env.sh +70 -0
  334. package/scripts/sync-github-about.js +52 -0
  335. package/scripts/sync-version.js +451 -0
  336. package/scripts/synthetic-dpo.js +234 -0
  337. package/scripts/telemetry-analytics.js +821 -0
  338. package/scripts/tessl-export.js +371 -0
  339. package/scripts/test-coverage.js +120 -0
  340. package/scripts/thompson-sampling.js +417 -0
  341. package/scripts/thumbgate-search.js +189 -0
  342. package/scripts/tool-kpi-tracker.js +12 -0
  343. package/scripts/tool-registry.js +811 -0
  344. package/scripts/train_from_feedback.py +910 -0
  345. package/scripts/user-profile.js +78 -0
  346. package/scripts/validate-feedback.js +580 -0
  347. package/scripts/validate-workflow-contract.js +287 -0
  348. package/scripts/vector-store.js +198 -0
  349. package/scripts/verification-loop.js +291 -0
  350. package/scripts/verify-obsidian-setup.sh +269 -0
  351. package/scripts/verify-run.js +269 -0
  352. package/scripts/webhook-delivery.js +62 -0
  353. package/scripts/weekly-auto-post.js +124 -0
  354. package/scripts/workflow-runs.js +154 -0
  355. package/scripts/workflow-sprint-intake.js +475 -0
  356. package/scripts/workspace-evolver.js +374 -0
  357. package/scripts/x-autonomous-marketing.js +139 -0
  358. package/scripts/xmemory-lite.js +405 -0
  359. package/skills/agent-memory/SKILL.md +97 -0
  360. package/skills/solve-architecture-autonomy/SKILL.md +17 -0
  361. package/skills/solve-architecture-autonomy/tool.js +33 -0
  362. package/skills/thumbgate/SKILL.md +114 -0
  363. package/skills/thumbgate-feedback/SKILL.md +49 -0
  364. package/src/api/server.js +4208 -0
@@ -0,0 +1,174 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const os = require('os');
5
+ const path = require('path');
6
+
7
+ const DEFAULT_PRO_API = 'https://thumbgate-production.up.railway.app';
8
+ const CREATOR_BYPASS_VALUE = process.env.THUMBGATE_DEV_SECRET || '';
9
+ const CREATOR_BYPASS_ENV = 'THUMBGATE_DEV_BYPASS';
10
+ const CREATOR_SYNTHETIC_KEY = process.env.THUMBGATE_DEV_KEY || '';
11
+
12
+ /**
13
+ * Creator/dogfooding bypass — returns true when the tool creator is running locally.
14
+ * Two layers (PostHog/Laravel pattern):
15
+ * 1. Config file: ~/.config/thumbgate/dev.json with {"bypass":"[set via THUMBGATE_DEV_SECRET env var]"}
16
+ * 2. Env var: THUMBGATE_DEV_BYPASS=[set via THUMBGATE_DEV_SECRET env var]
17
+ * Requires a specific non-obvious value (not boolean) to prevent accidental activation.
18
+ */
19
+ function isCreatorDev({ env = process.env, homeDir = os.homedir() } = {}) {
20
+ // Layer 1: env var with specific value
21
+ if (CREATOR_BYPASS_VALUE && String(env[CREATOR_BYPASS_ENV] || '') === CREATOR_BYPASS_VALUE) {
22
+ return true;
23
+ }
24
+ // Layer 2: persistent config file (set once, never think about it again)
25
+ try {
26
+ const configPath = path.join(homeDir, '.config', 'thumbgate', 'dev.json');
27
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
28
+ if (CREATOR_BYPASS_VALUE && config && config.bypass === CREATOR_BYPASS_VALUE) {
29
+ return true;
30
+ }
31
+ } catch { /* not a dev machine */ }
32
+ return false;
33
+ }
34
+
35
+ /**
36
+ * Developer override: returns true when ~/.config/thumbgate/dev.json exists
37
+ * with any non-empty bypass value. No env var needed — just the config file.
38
+ * Used by the server to skip auth on localhost during local development.
39
+ */
40
+ function hasDevOverride(homeDir = os.homedir()) {
41
+ // Disabled during test runs to avoid interfering with auth assertions
42
+ if (process.env.NODE_TEST_CONTEXT || process.env.THUMBGATE_TESTING) return false;
43
+ try {
44
+ const configPath = path.join(homeDir, '.config', 'thumbgate', 'dev.json');
45
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
46
+ return config && typeof config.bypass === 'string' && config.bypass.length > 0;
47
+ } catch { return false; }
48
+ }
49
+
50
+ function getLicenseDir(homeDir = os.homedir()) {
51
+ return path.join(homeDir, '.thumbgate');
52
+ }
53
+
54
+ function getLicensePath(homeDir = os.homedir()) {
55
+ return path.join(getLicenseDir(homeDir), 'license.json');
56
+ }
57
+
58
+ function readLicense({ homeDir } = {}) {
59
+ try {
60
+ return JSON.parse(fs.readFileSync(getLicensePath(homeDir), 'utf8'));
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ function saveLicense(key, { homeDir, version } = {}) {
67
+ const licenseDir = getLicenseDir(homeDir);
68
+ const licensePath = getLicensePath(homeDir);
69
+ fs.mkdirSync(licenseDir, { recursive: true });
70
+ fs.writeFileSync(
71
+ licensePath,
72
+ JSON.stringify({
73
+ key: String(key || '').trim(),
74
+ savedAt: new Date().toISOString(),
75
+ version: version || null,
76
+ }, null, 2) + '\n'
77
+ );
78
+ return licensePath;
79
+ }
80
+
81
+ function resolveProKey({ env = process.env, homeDir } = {}) {
82
+ // Creator bypass — unlocks Pro without any license key
83
+ if (isCreatorDev({ env, homeDir })) {
84
+ return {
85
+ key: CREATOR_SYNTHETIC_KEY,
86
+ source: 'creator-dev',
87
+ plan: 'enterprise',
88
+ };
89
+ }
90
+
91
+ const envKey = String(env.THUMBGATE_API_KEY || '').trim();
92
+ if (envKey) {
93
+ return {
94
+ key: envKey,
95
+ source: 'env',
96
+ };
97
+ }
98
+
99
+ const license = readLicense({ homeDir });
100
+ const licenseKey = String(license && license.key ? license.key : '').trim();
101
+ if (licenseKey) {
102
+ return {
103
+ key: licenseKey,
104
+ source: 'license',
105
+ licensePath: getLicensePath(homeDir),
106
+ };
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ async function validateProKey(key, { apiBaseUrl = DEFAULT_PRO_API, fetchImpl = globalThis.fetch } = {}) {
113
+ if (!key || typeof fetchImpl !== 'function') {
114
+ return false;
115
+ }
116
+
117
+ try {
118
+ const res = await fetchImpl(`${apiBaseUrl}/v1/billing/usage`, {
119
+ headers: {
120
+ 'Authorization': `Bearer ${String(key).trim()}`,
121
+ },
122
+ });
123
+ if (!res.ok) {
124
+ return false;
125
+ }
126
+ const data = await res.json().catch(() => ({}));
127
+ return Boolean(data && data.key);
128
+ } catch {
129
+ return false;
130
+ }
131
+ }
132
+
133
+ async function startLocalProDashboard({
134
+ key,
135
+ env = process.env,
136
+ port,
137
+ startServerImpl,
138
+ homeDir,
139
+ } = {}) {
140
+ const normalizedKey = String(key || '').trim();
141
+ if (!normalizedKey && !isCreatorDev({ env, homeDir })) {
142
+ throw new Error('Pro license key required.');
143
+ }
144
+
145
+ env.THUMBGATE_PRO_MODE = '1';
146
+ env.THUMBGATE_API_KEY = normalizedKey;
147
+
148
+ const desiredPort = Number(port ?? env.PORT ?? 3456);
149
+ env.PORT = String(desiredPort);
150
+
151
+ const startServer = startServerImpl || require(path.join(__dirname, '..', 'src', 'api', 'server')).startServer;
152
+ const handle = await startServer({ port: desiredPort });
153
+ return {
154
+ server: handle.server,
155
+ port: handle.port,
156
+ url: `http://localhost:${handle.port}/dashboard`,
157
+ };
158
+ }
159
+
160
+ module.exports = {
161
+ CREATOR_BYPASS_ENV,
162
+ CREATOR_BYPASS_VALUE,
163
+ CREATOR_SYNTHETIC_KEY,
164
+ DEFAULT_PRO_API,
165
+ getLicenseDir,
166
+ getLicensePath,
167
+ hasDevOverride,
168
+ isCreatorDev,
169
+ readLicense,
170
+ saveLicense,
171
+ resolveProKey,
172
+ validateProKey,
173
+ startLocalProDashboard,
174
+ };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * RFC 9457 Problem Detail helper for AI-agent-friendly error responses.
5
+ * @see https://www.rfc-editor.org/rfc/rfc9457
6
+ */
7
+
8
+ const PROBLEM_TYPES = {
9
+ RATE_LIMIT: 'urn:thumbgate:error:rate-limit-exceeded',
10
+ UNAUTHORIZED: 'urn:thumbgate:error:unauthorized',
11
+ FORBIDDEN: 'urn:thumbgate:error:forbidden',
12
+ NOT_FOUND: 'urn:thumbgate:error:not-found',
13
+ BAD_REQUEST: 'urn:thumbgate:error:bad-request',
14
+ INVALID_JSON: 'urn:thumbgate:error:invalid-json',
15
+ PAYMENT_REQUIRED: 'urn:thumbgate:error:payment-required',
16
+ INTERNAL: 'urn:thumbgate:error:internal-server-error',
17
+ WEBHOOK_INVALID: 'urn:thumbgate:error:webhook-invalid-signature',
18
+ SERVICE_UNAVAILABLE: 'urn:thumbgate:error:service-unavailable',
19
+ };
20
+
21
+ /**
22
+ * Build an RFC 9457 problem detail object.
23
+ * @param {object} opts
24
+ * @param {string} opts.type - URN from PROBLEM_TYPES
25
+ * @param {string} opts.title - Short human-readable summary
26
+ * @param {number} opts.status - HTTP status code
27
+ * @param {string} [opts.detail] - Longer explanation
28
+ * @param {string} [opts.instance] - URI reference for this specific occurrence
29
+ * @param {object} [opts.extensions] - Additional fields
30
+ * @returns {object}
31
+ */
32
+ function problemDetail({ type, title, status, detail, instance, ...extensions }) {
33
+ const obj = { type, title, status };
34
+ if (detail) obj.detail = detail;
35
+ if (instance) obj.instance = instance;
36
+ return { ...obj, ...extensions };
37
+ }
38
+
39
+ /**
40
+ * Send an RFC 9457 problem detail response via Node http.ServerResponse.
41
+ */
42
+ function sendProblem(res, opts, extraHeaders = {}) {
43
+ const problem = problemDetail(opts);
44
+ const body = JSON.stringify(problem);
45
+ res.writeHead(problem.status, {
46
+ 'Content-Type': 'application/problem+json; charset=utf-8',
47
+ 'Content-Length': Buffer.byteLength(body),
48
+ ...extraHeaders,
49
+ });
50
+ res.end(body);
51
+ }
52
+
53
+ module.exports = { problemDetail, sendProblem, PROBLEM_TYPES };
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('fs');
5
+ const https = require('https');
6
+ const path = require('path');
7
+
8
+ const { getFeedbackPaths } = require('./feedback-loop');
9
+
10
+ function buildProductIssueTitle(body, category = 'bug') {
11
+ const prefix = category === 'feature'
12
+ ? '[Feature] '
13
+ : category === 'question'
14
+ ? '[Question] '
15
+ : '[Bug] ';
16
+ const trimmed = String(body || '').trim();
17
+ return prefix + trimmed.slice(0, 80) + (trimmed.length > 80 ? '...' : '');
18
+ }
19
+
20
+ function buildProductIssueBody(body, category = 'bug', source = 'dashboard feedback widget') {
21
+ return [
22
+ '## User Feedback',
23
+ '',
24
+ String(body || '').trim(),
25
+ '',
26
+ '---',
27
+ `*Submitted via ${source}*`,
28
+ `*Category: ${category || 'bug'}*`,
29
+ ].join('\n');
30
+ }
31
+
32
+ function appendProductFeedbackLog(entry) {
33
+ const feedbackLogPath = path.join(getFeedbackPaths().FEEDBACK_DIR, 'user-feedback.jsonl');
34
+ const feedbackDir = path.dirname(feedbackLogPath);
35
+ if (!fs.existsSync(feedbackDir)) fs.mkdirSync(feedbackDir, { recursive: true });
36
+ fs.appendFileSync(feedbackLogPath, `${JSON.stringify(entry)}\n`);
37
+ return feedbackLogPath;
38
+ }
39
+
40
+ function createGithubIssue({ owner, repo, token, title, body, labels }) {
41
+ return new Promise((resolve, reject) => {
42
+ const requestBody = JSON.stringify({ title, body, labels });
43
+ const req = https.request({
44
+ hostname: 'api.github.com',
45
+ path: `/repos/${owner}/${repo}/issues`,
46
+ method: 'POST',
47
+ headers: {
48
+ Authorization: `token ${token}`,
49
+ 'Content-Type': 'application/json',
50
+ 'Content-Length': Buffer.byteLength(requestBody),
51
+ 'User-Agent': 'ThumbGate-Product-Feedback',
52
+ },
53
+ }, (res) => {
54
+ let responseBody = '';
55
+ res.on('data', (chunk) => {
56
+ responseBody += chunk;
57
+ });
58
+ res.on('end', () => {
59
+ try {
60
+ resolve(JSON.parse(responseBody));
61
+ } catch (error) {
62
+ reject(error);
63
+ }
64
+ });
65
+ });
66
+ req.on('error', reject);
67
+ req.write(requestBody);
68
+ req.end();
69
+ });
70
+ }
71
+
72
+ async function submitProductIssue({
73
+ title,
74
+ body,
75
+ category = 'bug',
76
+ source = 'dashboard feedback widget',
77
+ githubToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN,
78
+ repoFullName = 'IgorGanapolsky/ThumbGate',
79
+ } = {}) {
80
+ const trimmedTitle = String(title || '').trim();
81
+ const trimmedBody = String(body || '').trim();
82
+ if (!trimmedTitle) throw new Error('title required');
83
+ if (trimmedBody.length < 5) throw new Error('body too short');
84
+
85
+ const feedbackEntry = {
86
+ title: trimmedTitle,
87
+ body: trimmedBody,
88
+ category,
89
+ source,
90
+ timestamp: new Date().toISOString(),
91
+ };
92
+ appendProductFeedbackLog(feedbackEntry);
93
+
94
+ if (!githubToken) {
95
+ return {
96
+ success: true,
97
+ issueNumber: null,
98
+ issueUrl: null,
99
+ note: 'logged locally (no GitHub token)',
100
+ };
101
+ }
102
+
103
+ const [owner, repo] = String(repoFullName).split('/');
104
+ try {
105
+ const issue = await createGithubIssue({
106
+ owner,
107
+ repo,
108
+ token: githubToken,
109
+ title: trimmedTitle,
110
+ body: buildProductIssueBody(trimmedBody, category, source),
111
+ labels: ['user-feedback', category || 'bug'].filter(Boolean),
112
+ });
113
+ return {
114
+ success: true,
115
+ issueNumber: issue.number || null,
116
+ issueUrl: issue.html_url || null,
117
+ note: issue.number ? 'filed in GitHub' : 'logged locally',
118
+ };
119
+ } catch {
120
+ return {
121
+ success: true,
122
+ issueNumber: null,
123
+ issueUrl: null,
124
+ note: 'logged locally',
125
+ };
126
+ }
127
+ }
128
+
129
+ module.exports = {
130
+ appendProductFeedbackLog,
131
+ buildProductIssueBody,
132
+ buildProductIssueTitle,
133
+ submitProductIssue,
134
+ };
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Profile Router — OpenShell-inspired auto MCP profile selection
6
+ *
7
+ * Instead of manually setting THUMBGATE_MCP_PROFILE, this module analyzes the
8
+ * current context (tool name, input, session state) and automatically selects
9
+ * the most restrictive profile that still permits the required operation.
10
+ *
11
+ * Principle: deny-by-default, least-privilege, context-aware routing.
12
+ */
13
+
14
+ const path = require('path');
15
+ const {
16
+ getSetting,
17
+ getSettingOrigin,
18
+ } = require('./settings-hierarchy');
19
+ const { recommendInferenceBackend } = require('./local-model-profile');
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Context classifiers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Classifies the current operation context to determine the appropriate
27
+ * MCP profile. Returns the most restrictive profile that still allows
28
+ * the needed tools.
29
+ *
30
+ * @param {object} params
31
+ * @param {string} params.toolName — MCP tool being requested
32
+ * @param {object} [params.toolInput] — tool input payload
33
+ * @param {string} [params.sessionType] — 'review' | 'execute' | 'debug' | null
34
+ * @param {boolean} [params.hasWriteIntent] — whether the session involves writes
35
+ * @returns {{ profile: string, reason: string }}
36
+ */
37
+ function routeProfile(params = {}) {
38
+ const { toolName, sessionType, hasWriteIntent, settingsOptions } = params;
39
+ const explicitProfile = process.env.THUMBGATE_MCP_PROFILE;
40
+ const defaultProfile = getSetting('mcp.defaultProfile', settingsOptions) || 'essential';
41
+ const readonlyProfile = getSetting('mcp.readonlySessionProfile', settingsOptions) || 'readonly';
42
+ const defaultOrigin = getSettingOrigin('mcp.defaultProfile', settingsOptions);
43
+ const readonlyOrigin = getSettingOrigin('mcp.readonlySessionProfile', settingsOptions);
44
+
45
+ // Explicit override always wins — but we still audit the decision
46
+ if (explicitProfile) {
47
+ return {
48
+ profile: explicitProfile,
49
+ reason: `explicit override via THUMBGATE_MCP_PROFILE=${explicitProfile}`,
50
+ wasAutoRouted: false,
51
+ settingsOrigin: null,
52
+ };
53
+ }
54
+
55
+ // Session-type routing
56
+ if (sessionType === 'review' || isReadOnlySession()) {
57
+ return {
58
+ profile: readonlyProfile,
59
+ reason: `read-only session detected — routing to ${readonlyProfile} profile`,
60
+ wasAutoRouted: true,
61
+ settingsOrigin: readonlyOrigin,
62
+ };
63
+ }
64
+
65
+ // Tool-level routing: if the tool is only available in certain profiles,
66
+ // select the most restrictive one that includes it
67
+ if (toolName) {
68
+ const profile = findMostRestrictiveProfile(toolName);
69
+ if (profile) {
70
+ return {
71
+ profile,
72
+ reason: `auto-routed to "${profile}" — most restrictive profile containing "${toolName}"`,
73
+ wasAutoRouted: true,
74
+ };
75
+ }
76
+ }
77
+
78
+ // Write-intent routing
79
+ if (hasWriteIntent === false) {
80
+ return {
81
+ profile: readonlyProfile,
82
+ reason: `no write intent — routing to ${readonlyProfile} profile`,
83
+ wasAutoRouted: true,
84
+ settingsOrigin: readonlyOrigin,
85
+ };
86
+ }
87
+
88
+ // Default: use 'essential' instead of 'default' for least-privilege
89
+ return {
90
+ profile: defaultProfile,
91
+ reason: `default auto-routing — ${defaultProfile} profile from settings hierarchy`,
92
+ wasAutoRouted: true,
93
+ settingsOrigin: defaultOrigin,
94
+ };
95
+ }
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Profile analysis helpers
99
+ // ---------------------------------------------------------------------------
100
+
101
+ function loadProfilesLazy() {
102
+ try {
103
+ const mcpPolicy = require('./mcp-policy');
104
+ return mcpPolicy.loadMcpPolicy();
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Find the most restrictive (smallest) profile that includes the given tool.
112
+ * Profile restrictiveness = fewer tools allowed = more restricted.
113
+ */
114
+ function findMostRestrictiveProfile(toolName) {
115
+ const policy = loadProfilesLazy();
116
+ if (!policy || !policy.profiles) return null;
117
+
118
+ // Sort profiles by number of tools (most restrictive first)
119
+ const candidates = Object.entries(policy.profiles)
120
+ .filter(([, tools]) => tools.includes(toolName))
121
+ .sort((a, b) => a[1].length - b[1].length);
122
+
123
+ if (candidates.length === 0) return null;
124
+ return candidates[0][0]; // most restrictive profile that has this tool
125
+ }
126
+
127
+ /**
128
+ * Detect read-only session from environment signals.
129
+ */
130
+ function isReadOnlySession() {
131
+ // CI review context
132
+ if (process.env.CI && process.env.GITHUB_EVENT_NAME === 'pull_request') return true;
133
+ // Explicit read-only marker
134
+ if (process.env.THUMBGATE_SESSION_TYPE === 'review') return true;
135
+ // Subagent review profile
136
+ if (process.env.THUMBGATE_SUBAGENT_PROFILE === 'review_workflow') return true;
137
+ return false;
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Sensitive data routing (privacy router)
142
+ // ---------------------------------------------------------------------------
143
+
144
+ /**
145
+ * Determines whether a tool input should be routed to a local model
146
+ * (privacy-sensitive) or can go to a frontier model.
147
+ *
148
+ * @param {object} params
149
+ * @param {string} params.toolName
150
+ * @param {object} params.toolInput
151
+ * @returns {{ route: 'local' | 'frontier', reason: string }}
152
+ */
153
+ function routePrivacy(params = {}) {
154
+ const { toolName, toolInput } = params;
155
+ const inputStr = JSON.stringify(toolInput || {});
156
+
157
+ // Patterns that should stay local
158
+ const sensitivePatterns = [
159
+ /\.env\b/i,
160
+ /credentials?\b/i,
161
+ /secret[_-]?key/i,
162
+ /api[_-]?key/i,
163
+ /password/i,
164
+ /token/i,
165
+ /private[_-]?key/i,
166
+ /\.pem\b/i,
167
+ /\.p12\b/i,
168
+ /auth[_-]?config/i,
169
+ ];
170
+
171
+ // Check if input references sensitive material
172
+ for (const pattern of sensitivePatterns) {
173
+ if (pattern.test(inputStr) || pattern.test(toolName || '')) {
174
+ return {
175
+ route: 'local',
176
+ reason: `sensitive pattern detected: ${pattern.source}`,
177
+ };
178
+ }
179
+ }
180
+
181
+ // Sensitive tools that should always route locally
182
+ const localOnlyTools = [
183
+ 'capture_feedback',
184
+ 'export_dpo_pairs',
185
+ 'export_databricks_bundle',
186
+ 'track_action',
187
+ 'verify_claim',
188
+ 'register_claim_gate',
189
+ ];
190
+ if (localOnlyTools.includes(toolName)) {
191
+ return {
192
+ route: 'local',
193
+ reason: `tool "${toolName}" contains training data — routing locally`,
194
+ };
195
+ }
196
+
197
+ return {
198
+ route: 'frontier',
199
+ reason: 'no sensitive content detected — frontier routing allowed',
200
+ };
201
+ }
202
+
203
+ function routeInference(params = {}) {
204
+ const privacy = routePrivacy(params);
205
+ const inference = recommendInferenceBackend({
206
+ type: params.taskType,
207
+ contextTokens: params.contextTokens,
208
+ tags: params.tags,
209
+ privacyRoute: privacy.route,
210
+ }, params.env || process.env);
211
+
212
+ return {
213
+ route: privacy.route === 'local' ? 'local' : inference.route,
214
+ reason: inference.reason,
215
+ workloadClass: inference.workloadClass,
216
+ recommendationClass: inference.recommendationClass,
217
+ privacy,
218
+ backend: inference.backend,
219
+ };
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Exports
224
+ // ---------------------------------------------------------------------------
225
+
226
+ module.exports = {
227
+ routeProfile,
228
+ routePrivacy,
229
+ routeInference,
230
+ findMostRestrictiveProfile,
231
+ isReadOnlySession,
232
+ };
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // CLI
236
+ // ---------------------------------------------------------------------------
237
+
238
+ if (require.main === module) {
239
+ const toolName = process.argv[2] || null;
240
+ const result = routeProfile({ toolName });
241
+ const privacy = toolName ? routePrivacy({ toolName, toolInput: {} }) : null;
242
+ const inference = routeInference({ toolName, toolInput: {}, taskType: 'large-context', contextTokens: 200000, tags: ['xmemory'] });
243
+
244
+ console.log(JSON.stringify({ routing: result, privacy, inference }, null, 2));
245
+ }