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.
- package/.claude-plugin/README.md +134 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +18 -0
- package/.claude-plugin/bundle/server/index.js +24 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.well-known/mcp/server-card.json +231 -0
- package/LICENSE +21 -0
- package/README.md +375 -0
- package/adapters/README.md +9 -0
- package/adapters/amp/skills/thumbgate-feedback/SKILL.md +22 -0
- package/adapters/chatgpt/INSTALL.md +83 -0
- package/adapters/chatgpt/openapi.yaml +1281 -0
- package/adapters/claude/.mcp.json +14 -0
- package/adapters/codex/config.toml +9 -0
- package/adapters/gemini/function-declarations.json +224 -0
- package/adapters/mcp/server-stdio.js +788 -0
- package/adapters/opencode/opencode.json +15 -0
- package/bin/cli.js +1484 -0
- package/bin/memory.sh +64 -0
- package/bin/obsidian-sync.sh +20 -0
- package/bin/postinstall.js +37 -0
- package/config/build-metadata.json +4 -0
- package/config/e2e-critical-flows.json +45 -0
- package/config/gate-templates.json +77 -0
- package/config/gates/claim-verification.json +29 -0
- package/config/gates/computer-use.json +39 -0
- package/config/gates/default.json +117 -0
- package/config/github-about.json +25 -0
- package/config/mcp-allowlists.json +135 -0
- package/config/model-tiers.json +33 -0
- package/config/partner-routing.json +132 -0
- package/config/policy-bundles/constrained-v1.json +64 -0
- package/config/policy-bundles/default-v1.json +91 -0
- package/config/rubrics/default-v1.json +52 -0
- package/config/skill-packs/react-testing.json +23 -0
- package/config/skill-packs/stripe-integration/references/api-spec.json +1 -0
- package/config/skill-packs/stripe-integration/references/webhook-guide.md +3 -0
- package/config/skill-specs/pr-reviewer.json +9 -0
- package/config/skill-specs/release-status.json +9 -0
- package/config/skill-specs/ticket-triage.json +9 -0
- package/config/subagent-profiles.json +32 -0
- package/config/tessl-tiles.json +29 -0
- package/config/thumbgate-settings.managed.json +12 -0
- package/openapi/openapi.yaml +1281 -0
- package/package.json +283 -0
- package/plugins/amp-skill/INSTALL.md +52 -0
- package/plugins/amp-skill/SKILL.md +64 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +22 -0
- package/plugins/claude-codex-bridge/.mcp.json +12 -0
- package/plugins/claude-codex-bridge/INSTALL.md +43 -0
- package/plugins/claude-codex-bridge/README.md +46 -0
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +288 -0
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +24 -0
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +22 -0
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +28 -0
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +27 -0
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +21 -0
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +19 -0
- package/plugins/claude-skill/INSTALL.md +55 -0
- package/plugins/claude-skill/SKILL.md +46 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +43 -0
- package/plugins/codex-profile/.mcp.json +12 -0
- package/plugins/codex-profile/AGENTS.md +20 -0
- package/plugins/codex-profile/INSTALL.md +66 -0
- package/plugins/codex-profile/README.md +37 -0
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +23 -0
- package/plugins/cursor-marketplace/CHANGELOG.md +30 -0
- package/plugins/cursor-marketplace/LICENSE +21 -0
- package/plugins/cursor-marketplace/README.md +124 -0
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +31 -0
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +33 -0
- package/plugins/cursor-marketplace/commands/check-gates.md +25 -0
- package/plugins/cursor-marketplace/commands/show-lessons.md +27 -0
- package/plugins/cursor-marketplace/hooks/hooks.json +10 -0
- package/plugins/cursor-marketplace/mcp.json +12 -0
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +34 -0
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +30 -0
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +28 -0
- package/plugins/cursor-marketplace/scripts/gate-check.sh +11 -0
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +47 -0
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +31 -0
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +30 -0
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +33 -0
- package/plugins/gemini-extension/INSTALL.md +92 -0
- package/plugins/gemini-extension/gemini_prompt.txt +14 -0
- package/plugins/gemini-extension/tool_contract.json +45 -0
- package/plugins/opencode-profile/INSTALL.md +57 -0
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +400 -0
- package/public/dashboard.html +1093 -0
- package/public/guide.html +317 -0
- package/public/index.html +1014 -0
- package/public/learn/agent-harness-pattern.html +180 -0
- package/public/learn/ai-agent-persistent-memory.html +202 -0
- package/public/learn/learn.css +45 -0
- package/public/learn/mcp-pre-action-gates-explained.html +172 -0
- package/public/learn/stop-ai-agent-force-push.html +134 -0
- package/public/learn/vibe-coding-safety-net.html +142 -0
- package/public/learn.html +213 -0
- package/public/lessons.html +650 -0
- package/public/vercel.json +8 -0
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/a2ui-engine.js +73 -0
- package/scripts/access-anomaly-detector.js +12 -0
- package/scripts/adk-consolidator.js +266 -0
- package/scripts/agent-readiness.js +220 -0
- package/scripts/agent-security-hardening.js +227 -0
- package/scripts/agentic-data-pipeline.js +847 -0
- package/scripts/analytics-report.js +328 -0
- package/scripts/analytics-window.js +158 -0
- package/scripts/async-job-runner.js +1001 -0
- package/scripts/audit-trail.js +398 -0
- package/scripts/auto-promote-gates.js +299 -0
- package/scripts/auto-wire-hooks.js +312 -0
- package/scripts/autonomous-sales-agent.js +39 -0
- package/scripts/autoresearch-runner.js +216 -0
- package/scripts/background-agent-governance.js +237 -0
- package/scripts/behavioral-extraction.js +97 -0
- package/scripts/belief-update.js +84 -0
- package/scripts/billing.js +2438 -0
- package/scripts/bot-detector.js +50 -0
- package/scripts/budget-guard.js +173 -0
- package/scripts/build-claude-mcpb.js +189 -0
- package/scripts/build-metadata.js +97 -0
- package/scripts/check-congruence.js +322 -0
- package/scripts/cli-feedback.js +135 -0
- package/scripts/cli-telemetry.js +87 -0
- package/scripts/cloudflare-dynamic-sandbox.js +315 -0
- package/scripts/code-reasoning.js +350 -0
- package/scripts/codegraph-context.js +466 -0
- package/scripts/commercial-offer.js +56 -0
- package/scripts/computer-use-firewall.js +250 -0
- package/scripts/context-engine.js +694 -0
- package/scripts/contextfs.js +1287 -0
- package/scripts/conversation-context.js +119 -0
- package/scripts/creator-campaigns.js +239 -0
- package/scripts/daemon-manager.js +108 -0
- package/scripts/daily-digest.js +11 -0
- package/scripts/dashboard-render-spec.js +395 -0
- package/scripts/dashboard.js +1058 -0
- package/scripts/data-governance.js +173 -0
- package/scripts/delegation-runtime.js +900 -0
- package/scripts/deploy-gcp.sh +44 -0
- package/scripts/deploy-policy.js +263 -0
- package/scripts/disagreement-mining.js +315 -0
- package/scripts/dispatch-brief.js +159 -0
- package/scripts/distribution-surfaces.js +44 -0
- package/scripts/dpo-optimizer.js +209 -0
- package/scripts/ephemeral-agent-store.js +219 -0
- package/scripts/eval-harness.js +56 -0
- package/scripts/evolution-state.js +241 -0
- package/scripts/experiment-tracker.js +267 -0
- package/scripts/export-databricks-bundle.js +242 -0
- package/scripts/export-dpo-pairs.js +345 -0
- package/scripts/export-kto-pairs.js +310 -0
- package/scripts/export-training.js +448 -0
- package/scripts/failure-diagnostics.js +558 -0
- package/scripts/feedback-attribution.js +313 -0
- package/scripts/feedback-fallback.js +111 -0
- package/scripts/feedback-history-distiller.js +391 -0
- package/scripts/feedback-inbox-read.js +162 -0
- package/scripts/feedback-loop.js +1887 -0
- package/scripts/feedback-paths.js +145 -0
- package/scripts/feedback-quality.js +139 -0
- package/scripts/feedback-root-consolidator.js +238 -0
- package/scripts/feedback-schema.js +426 -0
- package/scripts/feedback-session.js +286 -0
- package/scripts/feedback-to-memory.js +185 -0
- package/scripts/feedback-to-rules.js +163 -0
- package/scripts/filesystem-search.js +404 -0
- package/scripts/funnel-analytics.js +35 -0
- package/scripts/gate-satisfy.js +42 -0
- package/scripts/gate-stats.js +116 -0
- package/scripts/gate-templates.js +70 -0
- package/scripts/gates-engine.js +816 -0
- package/scripts/generate-paperbanana-diagrams.sh +99 -0
- package/scripts/generate-pretool-hook.sh +40 -0
- package/scripts/github-about.js +350 -0
- package/scripts/github-outreach.js +65 -0
- package/scripts/gtm-revenue-loop.js +520 -0
- package/scripts/hallucination-detector.js +226 -0
- package/scripts/hf-papers.js +317 -0
- package/scripts/history-distiller.js +200 -0
- package/scripts/hook-auto-capture.sh +95 -0
- package/scripts/hook-stop-pr-thread-check.sh +68 -0
- package/scripts/hook-stop-self-score.sh +51 -0
- package/scripts/hook-stop-verify-deploy.sh +31 -0
- package/scripts/hook-thumbgate-cache-updater.js +48 -0
- package/scripts/hook-verify-before-done.sh +20 -0
- package/scripts/hosted-config.js +170 -0
- package/scripts/hybrid-feedback-context.js +676 -0
- package/scripts/install-mcp.js +159 -0
- package/scripts/intent-router.js +392 -0
- package/scripts/internal-agent-bootstrap.js +490 -0
- package/scripts/jsonl-watcher.js +155 -0
- package/scripts/lesson-db.js +613 -0
- package/scripts/lesson-inference.js +315 -0
- package/scripts/lesson-retrieval.js +95 -0
- package/scripts/lesson-rotation.js +137 -0
- package/scripts/lesson-search.js +644 -0
- package/scripts/lesson-synthesis.js +196 -0
- package/scripts/license.js +50 -0
- package/scripts/local-model-profile.js +383 -0
- package/scripts/markdown-escape.js +12 -0
- package/scripts/marketing-experiment.js +671 -0
- package/scripts/mcp-config.js +149 -0
- package/scripts/mcp-policy.js +99 -0
- package/scripts/memalign-recall.js +111 -0
- package/scripts/memory-firewall.js +222 -0
- package/scripts/memory-migration.js +296 -0
- package/scripts/meta-policy.js +194 -0
- package/scripts/metered-billing.js +16 -0
- package/scripts/model-tier-router.js +301 -0
- package/scripts/money-watcher.js +71 -0
- package/scripts/multi-hop-recall.js +240 -0
- package/scripts/natural-language-harness.js +330 -0
- package/scripts/obsidian-export.js +712 -0
- package/scripts/operational-dashboard.js +103 -0
- package/scripts/operational-summary.js +93 -0
- package/scripts/optimize-context.js +17 -0
- package/scripts/org-dashboard.js +201 -0
- package/scripts/partner-orchestration.js +146 -0
- package/scripts/per-step-scoring.js +165 -0
- package/scripts/perplexity-marketing.js +466 -0
- package/scripts/pii-scanner.js +153 -0
- package/scripts/plan-gate.js +154 -0
- package/scripts/post-everywhere.js +308 -0
- package/scripts/post-to-x-retry.sh +22 -0
- package/scripts/post-to-x.js +369 -0
- package/scripts/pr-manager.js +236 -0
- package/scripts/predictive-insights.js +356 -0
- package/scripts/principle-extractor.js +162 -0
- package/scripts/pro-features.js +40 -0
- package/scripts/pro-local-dashboard.js +174 -0
- package/scripts/problem-detail.js +53 -0
- package/scripts/product-feedback.js +134 -0
- package/scripts/profile-router.js +245 -0
- package/scripts/prompt-dlp.js +221 -0
- package/scripts/prompt-guard.js +83 -0
- package/scripts/prove-adapters.js +863 -0
- package/scripts/prove-attribution.js +365 -0
- package/scripts/prove-automation.js +653 -0
- package/scripts/prove-autoresearch.js +304 -0
- package/scripts/prove-claim-verification.js +277 -0
- package/scripts/prove-cloudflare-sandbox.js +163 -0
- package/scripts/prove-data-pipeline.js +410 -0
- package/scripts/prove-data-quality.js +227 -0
- package/scripts/prove-evolution.js +352 -0
- package/scripts/prove-harnesses.js +287 -0
- package/scripts/prove-intelligence.js +259 -0
- package/scripts/prove-lancedb.js +371 -0
- package/scripts/prove-local-intelligence.js +342 -0
- package/scripts/prove-loop-closure.js +263 -0
- package/scripts/prove-predictive-insights.js +357 -0
- package/scripts/prove-runtime.js +350 -0
- package/scripts/prove-seo-gsd.js +234 -0
- package/scripts/prove-settings.js +279 -0
- package/scripts/prove-subway-upgrades.js +277 -0
- package/scripts/prove-tessl.js +229 -0
- package/scripts/prove-training-export.js +327 -0
- package/scripts/prove-workflow-contract.js +116 -0
- package/scripts/prove-xmemory.js +332 -0
- package/scripts/publish-decision.js +133 -0
- package/scripts/pulse.js +80 -0
- package/scripts/rate-limiter.js +125 -0
- package/scripts/reddit-dm-outreach.js +182 -0
- package/scripts/reddit-monitor-cron.sh +26 -0
- package/scripts/reflector-agent.js +221 -0
- package/scripts/reminder-engine.js +132 -0
- package/scripts/revenue-status.js +472 -0
- package/scripts/risk-scorer.js +458 -0
- package/scripts/rlaif-self-audit.js +129 -0
- package/scripts/rubric-engine.js +230 -0
- package/scripts/schedule-manager.js +251 -0
- package/scripts/secret-scanner.js +414 -0
- package/scripts/self-heal.js +147 -0
- package/scripts/self-healing-check.js +188 -0
- package/scripts/semantic-layer.js +98 -0
- package/scripts/seo-gsd.js +1153 -0
- package/scripts/settings-hierarchy.js +214 -0
- package/scripts/shieldcortex-memory-firewall-runner.mjs +53 -0
- package/scripts/skill-exporter.js +262 -0
- package/scripts/skill-generator.js +446 -0
- package/scripts/skill-materializer.js +134 -0
- package/scripts/skill-packs.js +136 -0
- package/scripts/skill-proposer.js +99 -0
- package/scripts/skill-quality-tracker.js +284 -0
- package/scripts/slo-alert-engine.js +14 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/social-analytics/db/schema.sql +32 -0
- package/scripts/social-analytics/digest.js +256 -0
- package/scripts/social-analytics/generate-instagram-card.js +97 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +73 -0
- package/scripts/social-analytics/mcp-server.js +289 -0
- package/scripts/social-analytics/normalizer.js +580 -0
- package/scripts/social-analytics/notify.js +162 -0
- package/scripts/social-analytics/poll-all.js +107 -0
- package/scripts/social-analytics/pollers/github.js +195 -0
- package/scripts/social-analytics/pollers/instagram.js +253 -0
- package/scripts/social-analytics/pollers/linkedin.js +330 -0
- package/scripts/social-analytics/pollers/plausible.js +247 -0
- package/scripts/social-analytics/pollers/reddit.js +306 -0
- package/scripts/social-analytics/pollers/threads.js +233 -0
- package/scripts/social-analytics/pollers/tiktok.js +203 -0
- package/scripts/social-analytics/pollers/x.js +227 -0
- package/scripts/social-analytics/pollers/youtube.js +304 -0
- package/scripts/social-analytics/pollers/zernio.js +180 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +85 -0
- package/scripts/social-analytics/publishers/devto.js +122 -0
- package/scripts/social-analytics/publishers/instagram.js +317 -0
- package/scripts/social-analytics/publishers/linkedin.js +294 -0
- package/scripts/social-analytics/publishers/reddit.js +390 -0
- package/scripts/social-analytics/publishers/threads.js +275 -0
- package/scripts/social-analytics/publishers/tiktok.js +217 -0
- package/scripts/social-analytics/publishers/x.js +259 -0
- package/scripts/social-analytics/publishers/youtube.js +223 -0
- package/scripts/social-analytics/publishers/zernio.js +209 -0
- package/scripts/social-analytics/run-digest.js +34 -0
- package/scripts/social-analytics/store.js +257 -0
- package/scripts/social-analytics/utm.js +143 -0
- package/scripts/social-pipeline.js +2628 -0
- package/scripts/social-quality-gate.js +18 -0
- package/scripts/social-reply-monitor.js +445 -0
- package/scripts/status-dashboard.js +155 -0
- package/scripts/statusline-lesson.js +16 -0
- package/scripts/statusline-tower.js +8 -0
- package/scripts/statusline.sh +116 -0
- package/scripts/stripe-live-status.js +115 -0
- package/scripts/subagent-profiles.js +79 -0
- package/scripts/sync-gh-secrets-from-env.sh +70 -0
- package/scripts/sync-github-about.js +52 -0
- package/scripts/sync-version.js +451 -0
- package/scripts/synthetic-dpo.js +234 -0
- package/scripts/telemetry-analytics.js +821 -0
- package/scripts/tessl-export.js +371 -0
- package/scripts/test-coverage.js +120 -0
- package/scripts/thompson-sampling.js +417 -0
- package/scripts/thumbgate-search.js +189 -0
- package/scripts/tool-kpi-tracker.js +12 -0
- package/scripts/tool-registry.js +811 -0
- package/scripts/train_from_feedback.py +910 -0
- package/scripts/user-profile.js +78 -0
- package/scripts/validate-feedback.js +580 -0
- package/scripts/validate-workflow-contract.js +287 -0
- package/scripts/vector-store.js +198 -0
- package/scripts/verification-loop.js +291 -0
- package/scripts/verify-obsidian-setup.sh +269 -0
- package/scripts/verify-run.js +269 -0
- package/scripts/webhook-delivery.js +62 -0
- package/scripts/weekly-auto-post.js +124 -0
- package/scripts/workflow-runs.js +154 -0
- package/scripts/workflow-sprint-intake.js +475 -0
- package/scripts/workspace-evolver.js +374 -0
- package/scripts/x-autonomous-marketing.js +139 -0
- package/scripts/xmemory-lite.js +405 -0
- package/skills/agent-memory/SKILL.md +97 -0
- package/skills/solve-architecture-autonomy/SKILL.md +17 -0
- package/skills/solve-architecture-autonomy/tool.js +33 -0
- package/skills/thumbgate/SKILL.md +114 -0
- package/skills/thumbgate-feedback/SKILL.md +49 -0
- 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
|
+
}
|