thumbgate 0.9.10 → 0.9.11
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 +2 -2
- package/.claude-plugin/marketplace.json +4 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +115 -312
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +4 -4
- package/adapters/mcp/server-stdio.js +61 -1
- package/adapters/opencode/opencode.json +4 -2
- package/bin/cli.js +156 -8
- package/bin/memory.sh +3 -3
- package/config/e2e-critical-flows.json +4 -0
- package/config/gates/default.json +74 -2
- package/config/github-about.json +1 -1
- package/config/mcp-allowlists.json +27 -0
- package/package.json +22 -5
- package/plugins/amp-skill/INSTALL.md +1 -0
- package/plugins/amp-skill/SKILL.md +1 -0
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +4 -2
- package/plugins/claude-skill/INSTALL.md +1 -0
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +4 -2
- package/plugins/codex-profile/INSTALL.md +1 -1
- package/plugins/codex-profile/README.md +1 -1
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +3 -3
- package/plugins/cursor-marketplace/mcp.json +3 -1
- package/plugins/cursor-marketplace/scripts/gate-check.sh +15 -5
- package/plugins/gemini-extension/INSTALL.md +3 -3
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/dashboard.html +15 -8
- package/public/index.html +125 -185
- package/public/js/buyer-intent.js +252 -0
- package/public/pro.html +1085 -0
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/adk-consolidator.js +14 -2
- package/scripts/agent-readiness.js +3 -1
- package/scripts/agent-security-hardening.js +4 -4
- package/scripts/auto-promote-gates.js +2 -0
- package/scripts/auto-wire-hooks.js +105 -17
- package/scripts/behavioral-extraction.js +2 -6
- package/scripts/billing.js +107 -3
- package/scripts/budget-guard.js +2 -2
- package/scripts/build-metadata.js +14 -0
- package/scripts/context-engine.js +1 -0
- package/scripts/deploy-policy.js +3 -17
- package/scripts/dpo-optimizer.js +3 -6
- package/scripts/ensure-repo-bootstrap.js +129 -0
- package/scripts/export-dpo-pairs.js +2 -3
- package/scripts/export-kto-pairs.js +3 -4
- package/scripts/export-training.js +8 -6
- package/scripts/feedback-attribution.js +23 -11
- package/scripts/feedback-loop.js +40 -2
- package/scripts/feedback-to-rules.js +2 -1
- package/scripts/filesystem-search.js +3 -2
- package/scripts/gates-engine.js +760 -29
- package/scripts/generate-pretool-hook.sh +0 -0
- package/scripts/gtm-revenue-loop.js +20 -1
- package/scripts/hook-auto-capture.sh +8 -3
- package/scripts/hook-runtime.js +89 -0
- package/scripts/hook-stop-self-score.sh +3 -3
- package/scripts/hook-thumbgate-cache-updater.js +99 -38
- package/scripts/hosted-config.js +4 -16
- package/scripts/hybrid-feedback-context.js +54 -14
- package/scripts/install-mcp.js +13 -0
- package/scripts/intent-router.js +2 -2
- package/scripts/license.js +52 -14
- package/scripts/local-model-profile.js +3 -2
- package/scripts/mcp-config.js +68 -6
- package/scripts/meta-policy.js +4 -8
- package/scripts/money-watcher.js +166 -16
- package/scripts/obsidian-export.js +1 -0
- package/scripts/operational-integrity.js +480 -0
- package/scripts/post-everywhere.js +7 -12
- package/scripts/pr-manager.js +14 -11
- package/scripts/profile-router.js +2 -0
- package/scripts/prompt-dlp.js +1 -0
- package/scripts/publish-decision.js +10 -0
- package/scripts/published-cli.js +34 -0
- package/scripts/risk-scorer.js +3 -2
- package/scripts/rlhf_session_start.sh +32 -0
- package/scripts/skill-quality-tracker.js +3 -5
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- package/scripts/social-analytics/db/social-analytics.db-wal +0 -0
- package/scripts/social-analytics/engagement-audit.js +202 -0
- package/scripts/social-analytics/instagram-thumbgate-post.js +45 -7
- package/scripts/social-analytics/install-growth-automation.js +114 -0
- package/scripts/social-analytics/load-env.js +46 -0
- package/scripts/social-analytics/poll-all.js +3 -18
- package/scripts/social-analytics/pollers/zernio.js +3 -0
- package/scripts/social-analytics/publish-instagram-thumbgate.js +22 -3
- package/scripts/social-analytics/publish-thumbgate-launch.js +316 -0
- package/scripts/social-analytics/publishers/reddit.js +7 -12
- package/scripts/social-analytics/publishers/zernio.js +210 -22
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +165 -0
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +275 -0
- package/scripts/social-analytics/sync-launch-assets.js +185 -0
- package/scripts/social-post-hourly.js +185 -0
- package/scripts/social-quality-gate.js +119 -3
- package/scripts/social-reply-monitor.js +148 -32
- package/scripts/statusline-cache-path.js +27 -0
- package/scripts/statusline-meta.js +22 -0
- package/scripts/statusline.sh +24 -32
- package/scripts/sync-version.js +11 -3
- package/scripts/test-coverage.js +20 -13
- package/scripts/tool-registry.js +97 -0
- package/scripts/train_from_feedback.py +32 -9
- package/scripts/validate-feedback.js +3 -2
- package/scripts/vector-store.js +2 -3
- package/scripts/verify-obsidian-setup.sh +3 -3
- package/src/api/server.js +281 -33
|
File without changes
|
|
@@ -4,13 +4,20 @@
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const { spawnSync } = require('node:child_process');
|
|
7
|
-
const { GoogleGenAI } = require('@google/genai');
|
|
8
7
|
const { resolveHostedBillingConfig } = require('./hosted-config');
|
|
9
8
|
const { getOperationalBillingSummary } = require('./operational-summary');
|
|
10
9
|
|
|
11
10
|
const COMMERCIAL_TRUTH_LINK = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/COMMERCIAL_TRUTH.md';
|
|
12
11
|
const VERIFICATION_EVIDENCE_LINK = 'https://github.com/IgorGanapolsky/ThumbGate/blob/main/docs/VERIFICATION_EVIDENCE.md';
|
|
13
12
|
|
|
13
|
+
function getGoogleGenAI() {
|
|
14
|
+
try {
|
|
15
|
+
return require('@google/genai').GoogleGenAI;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
function parseArgs(argv = []) {
|
|
15
22
|
const options = {
|
|
16
23
|
maxTargets: 6,
|
|
@@ -310,6 +317,18 @@ async function generateOutreachMessages(targets, motionCatalog = buildMotionCata
|
|
|
310
317
|
});
|
|
311
318
|
}
|
|
312
319
|
|
|
320
|
+
const GoogleGenAI = getGoogleGenAI();
|
|
321
|
+
if (!GoogleGenAI) {
|
|
322
|
+
return targets.map((target) => {
|
|
323
|
+
const selectedMotion = selectOutreachMotion(target, motionCatalog);
|
|
324
|
+
return {
|
|
325
|
+
...target,
|
|
326
|
+
selectedMotion,
|
|
327
|
+
message: buildFallbackMessage(target, selectedMotion, motionCatalog),
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
313
332
|
const ai = new GoogleGenAI({ apiKey });
|
|
314
333
|
const results = [];
|
|
315
334
|
|
|
@@ -7,8 +7,13 @@ PROMPT="$CLAUDE_USER_PROMPT"
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
8
|
CAPTURE="$SCRIPT_DIR/../.claude/scripts/feedback/capture-feedback.js"
|
|
9
9
|
PROMPT_GUARD="$SCRIPT_DIR/prompt-guard.js"
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
ACTIVE_CWD="${CLAUDE_PROJECT_DIR:-${PWD:-$(pwd)}}"
|
|
11
|
+
FEEDBACK_DIR="$(node -e "const path = require('path'); const { resolveFeedbackDir } = require(path.join(process.argv[1], 'feedback-paths.js')); process.stdout.write(resolveFeedbackDir({ cwd: process.argv[2] || process.cwd(), feedbackDir: process.env.THUMBGATE_FEEDBACK_DIR || undefined }));" "$SCRIPT_DIR" "$ACTIVE_CWD" 2>/dev/null)"
|
|
12
|
+
if [ -z "$FEEDBACK_DIR" ]; then
|
|
13
|
+
FEEDBACK_DIR="${THUMBGATE_FEEDBACK_DIR:-$ACTIVE_CWD/.rlhf}"
|
|
14
|
+
fi
|
|
15
|
+
FEEDBACK_LOG="$FEEDBACK_DIR/feedback-log.jsonl"
|
|
16
|
+
MEMORY_LOG="$FEEDBACK_DIR/memory-log.jsonl"
|
|
12
17
|
|
|
13
18
|
# Record the latest user prompt so statusline thumbs can distill lessons
|
|
14
19
|
# from recent conversation even when the click itself has no body payload.
|
|
@@ -49,7 +54,7 @@ capture_and_report() {
|
|
|
49
54
|
echo "Storage Proof:"
|
|
50
55
|
echo " Feedback log : $FEEDBACK_LOG ($(wc -l < "$FEEDBACK_LOG" 2>/dev/null || echo 0) entries)"
|
|
51
56
|
echo " Memory log : $MEMORY_LOG ($(wc -l < "$MEMORY_LOG" 2>/dev/null || echo 0) entries)"
|
|
52
|
-
echo " LanceDB : $
|
|
57
|
+
echo " LanceDB : $FEEDBACK_DIR/lancedb/"
|
|
53
58
|
echo ""
|
|
54
59
|
|
|
55
60
|
# Show last entry written
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const {
|
|
6
|
+
isSourceCheckout,
|
|
7
|
+
publishedCliAvailable,
|
|
8
|
+
} = require('./mcp-config');
|
|
9
|
+
const { runPublishedCliHelp, publishedCliArgs } = require('./published-cli');
|
|
10
|
+
|
|
11
|
+
const PKG_ROOT = path.join(__dirname, '..');
|
|
12
|
+
const featureSupportCache = new Map();
|
|
13
|
+
|
|
14
|
+
function packageVersion() {
|
|
15
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
|
|
16
|
+
return pkg.version;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function shellQuote(value) {
|
|
20
|
+
return JSON.stringify(String(value));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function publishedHookCommandsAvailable(version) {
|
|
24
|
+
if (!publishedCliAvailable(version)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (featureSupportCache.has(version)) {
|
|
28
|
+
return featureSupportCache.get(version);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let available = false;
|
|
32
|
+
try {
|
|
33
|
+
const helpText = runPublishedCliHelp(version, { timeout: 8000 });
|
|
34
|
+
available = ['gate-check', 'cache-update', 'statusline-render', 'hook-auto-capture', 'session-start']
|
|
35
|
+
.every((command) => helpText.includes(command));
|
|
36
|
+
} catch {
|
|
37
|
+
available = false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
featureSupportCache.set(version, available);
|
|
41
|
+
return available;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveCliBaseCommand() {
|
|
45
|
+
const version = packageVersion();
|
|
46
|
+
if (publishedHookCommandsAvailable(version)) {
|
|
47
|
+
return `npx ${publishedCliArgs(version).map(shellQuote).join(' ')}`;
|
|
48
|
+
}
|
|
49
|
+
if (isSourceCheckout(PKG_ROOT)) {
|
|
50
|
+
return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
|
|
51
|
+
}
|
|
52
|
+
return `npx -y thumbgate@${version}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function buildPortableHookCommand(subcommand) {
|
|
56
|
+
return `${resolveCliBaseCommand()} ${subcommand}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function preToolHookCommand() {
|
|
60
|
+
return buildPortableHookCommand('gate-check');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function userPromptHookCommand() {
|
|
64
|
+
return buildPortableHookCommand('hook-auto-capture');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function sessionStartHookCommand() {
|
|
68
|
+
return buildPortableHookCommand('session-start');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cacheUpdateHookCommand() {
|
|
72
|
+
return buildPortableHookCommand('cache-update');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function statuslineCommand() {
|
|
76
|
+
return buildPortableHookCommand('statusline-render');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
buildPortableHookCommand,
|
|
81
|
+
cacheUpdateHookCommand,
|
|
82
|
+
packageVersion,
|
|
83
|
+
publishedHookCommandsAvailable,
|
|
84
|
+
preToolHookCommand,
|
|
85
|
+
resolveCliBaseCommand,
|
|
86
|
+
sessionStartHookCommand,
|
|
87
|
+
statuslineCommand,
|
|
88
|
+
userPromptHookCommand,
|
|
89
|
+
};
|
|
@@ -18,9 +18,9 @@ node -e '
|
|
|
18
18
|
const path = require("path");
|
|
19
19
|
|
|
20
20
|
// Resolve modules relative to ThumbGate package root
|
|
21
|
-
const
|
|
22
|
-
const { selfAuditAndLog } = require(path.join(
|
|
23
|
-
const { getFeedbackPaths } = require(path.join(
|
|
21
|
+
const thumbgateRoot = process.env.THUMBGATE_ROOT;
|
|
22
|
+
const { selfAuditAndLog } = require(path.join(thumbgateRoot, "scripts", "rlaif-self-audit"));
|
|
23
|
+
const { getFeedbackPaths } = require(path.join(thumbgateRoot, "scripts", "feedback-loop"));
|
|
24
24
|
|
|
25
25
|
const stopReason = process.env.CLAUDE_STOP_REASON || "unknown";
|
|
26
26
|
|
|
@@ -1,48 +1,109 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
|
-
* PostToolUse hook: updates ThumbGate statusline cache after
|
|
4
|
-
*
|
|
5
|
+
* PostToolUse hook: updates ThumbGate statusline cache after dashboard/stat calls.
|
|
6
|
+
* Also used directly by the CLI to refresh statusline counters after feedback capture.
|
|
5
7
|
*/
|
|
6
|
-
|
|
8
|
+
|
|
7
9
|
const fs = require('fs');
|
|
8
10
|
const path = require('path');
|
|
11
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
function getCachePath() {
|
|
14
|
+
return path.join(resolveFeedbackDir(), 'statusline_cache.json');
|
|
15
|
+
}
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
process.stdin.setEncoding('utf8');
|
|
15
|
-
process.stdin.on('data', chunk => { input += chunk; });
|
|
16
|
-
process.stdin.on('end', () => {
|
|
17
|
+
function readExistingCache(cachePath = getCachePath()) {
|
|
17
18
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
return JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
20
|
+
} catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function writeStatuslineCache(nextCache, cachePath = getCachePath()) {
|
|
26
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
27
|
+
fs.writeFileSync(cachePath, JSON.stringify(nextCache));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeDashboardPayload(payload = {}) {
|
|
31
|
+
const approval = payload.approval || {};
|
|
32
|
+
return {
|
|
33
|
+
thumbs_up: String(approval.totalPositive || 0),
|
|
34
|
+
thumbs_down: String(approval.totalNegative || 0),
|
|
35
|
+
lessons: String((payload.rubric || {}).samples || 0),
|
|
36
|
+
approval_rate: String(approval.approvalRate || '?'),
|
|
37
|
+
trend: approval.trendDirection || '?',
|
|
38
|
+
total_feedback: String(approval.total || 0),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function normalizeStatsPayload(payload = {}) {
|
|
43
|
+
return {
|
|
44
|
+
thumbs_up: String(payload.totalPositive || 0),
|
|
45
|
+
thumbs_down: String(payload.totalNegative || 0),
|
|
46
|
+
lessons: String((payload.rubric || {}).samples || 0),
|
|
47
|
+
approval_rate: String(Math.round((payload.approvalRate || 0) * 1000) / 10),
|
|
48
|
+
trend: payload.trend || '?',
|
|
49
|
+
total_feedback: String(payload.total || 0),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function refreshStatuslineCache(statsPayload = {}, cachePath = getCachePath()) {
|
|
54
|
+
const cache = {
|
|
55
|
+
...readExistingCache(cachePath),
|
|
56
|
+
...normalizeStatsPayload(statsPayload),
|
|
57
|
+
updated_at: String(Math.floor(Date.now() / 1000)),
|
|
58
|
+
};
|
|
59
|
+
writeStatuslineCache(cache, cachePath);
|
|
60
|
+
return cache;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateCacheFromEvent(event = {}, cachePath = getCachePath()) {
|
|
64
|
+
const tool = event.tool_name || '';
|
|
65
|
+
if (tool !== 'mcp__thumbgate__feedback_stats' && tool !== 'mcp__thumbgate__dashboard') {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const raw = event.tool_response;
|
|
70
|
+
if (!raw) return null;
|
|
71
|
+
const data = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
72
|
+
const cache = {
|
|
73
|
+
...readExistingCache(cachePath),
|
|
74
|
+
...(tool === 'mcp__thumbgate__feedback_stats'
|
|
75
|
+
? normalizeStatsPayload(data)
|
|
76
|
+
: normalizeDashboardPayload(data)),
|
|
77
|
+
updated_at: String(Math.floor(Date.now() / 1000)),
|
|
78
|
+
};
|
|
79
|
+
writeStatuslineCache(cache, cachePath);
|
|
80
|
+
return cache;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function runFromStdin() {
|
|
84
|
+
let input = '';
|
|
85
|
+
process.stdin.setEncoding('utf8');
|
|
86
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
87
|
+
process.stdin.on('end', () => {
|
|
88
|
+
try {
|
|
89
|
+
updateCacheFromEvent(input ? JSON.parse(input) : {});
|
|
90
|
+
} catch {
|
|
91
|
+
/* statusline cache is best-effort */
|
|
42
92
|
}
|
|
43
|
-
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (require.main === module) {
|
|
97
|
+
runFromStdin();
|
|
98
|
+
}
|
|
44
99
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
100
|
+
module.exports = {
|
|
101
|
+
getCachePath,
|
|
102
|
+
normalizeDashboardPayload,
|
|
103
|
+
normalizeStatsPayload,
|
|
104
|
+
readExistingCache,
|
|
105
|
+
refreshStatuslineCache,
|
|
106
|
+
runFromStdin,
|
|
107
|
+
updateCacheFromEvent,
|
|
108
|
+
writeStatuslineCache,
|
|
109
|
+
};
|
package/scripts/hosted-config.js
CHANGED
|
@@ -12,12 +12,6 @@ const DEFAULT_CHECKOUT_FALLBACK_URL = PRO_MONTHLY_PAYMENT_LINK;
|
|
|
12
12
|
const DEFAULT_PRO_PRICE_DOLLARS = PRO_MONTHLY_PRICE_DOLLARS;
|
|
13
13
|
const DEFAULT_PRO_PRICE_LABEL = PRO_PRICE_LABEL;
|
|
14
14
|
const GA_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]+$/i;
|
|
15
|
-
const PUBLIC_APP_ORIGIN_KEYS = ['THUMBGATE_PUBLIC_APP_ORIGIN', 'RLHF_PUBLIC_APP_ORIGIN'];
|
|
16
|
-
const BILLING_API_BASE_URL_KEYS = [
|
|
17
|
-
'THUMBGATE_BILLING_API_BASE_URL',
|
|
18
|
-
'RLHF_BILLING_API_BASE_URL',
|
|
19
|
-
'THUMBGATE_CANONICAL_API_BASE_URL',
|
|
20
|
-
];
|
|
21
15
|
|
|
22
16
|
function normalizeOrigin(value) {
|
|
23
17
|
if (!value || typeof value !== 'string') {
|
|
@@ -82,14 +76,8 @@ function normalizeTrackingId(value, pattern) {
|
|
|
82
76
|
return trimmed;
|
|
83
77
|
}
|
|
84
78
|
|
|
85
|
-
function resolveNormalizedOrigin(
|
|
86
|
-
|
|
87
|
-
const normalized = normalizeOrigin(env[key]);
|
|
88
|
-
if (normalized) {
|
|
89
|
-
return normalized;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return normalizeOrigin(fallback);
|
|
79
|
+
function resolveNormalizedOrigin(value, fallback = '') {
|
|
80
|
+
return normalizeOrigin(value) || normalizeOrigin(fallback);
|
|
93
81
|
}
|
|
94
82
|
|
|
95
83
|
function joinPublicUrl(baseOrigin, pathname) {
|
|
@@ -130,8 +118,8 @@ function buildHostedCancelUrl(appOrigin, traceId) {
|
|
|
130
118
|
|
|
131
119
|
function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
|
|
132
120
|
const inferredOrigin = normalizeOrigin(requestOrigin) || DEFAULT_PUBLIC_APP_ORIGIN;
|
|
133
|
-
const appOrigin = resolveNormalizedOrigin(env,
|
|
134
|
-
const billingApiBaseUrl = resolveNormalizedOrigin(env,
|
|
121
|
+
const appOrigin = resolveNormalizedOrigin(env.THUMBGATE_PUBLIC_APP_ORIGIN, inferredOrigin) || inferredOrigin;
|
|
122
|
+
const billingApiBaseUrl = resolveNormalizedOrigin(env.THUMBGATE_BILLING_API_BASE_URL, appOrigin) || appOrigin;
|
|
135
123
|
const proPriceDollars = normalizePriceDollars(env.THUMBGATE_PRO_PRICE_DOLLARS) || DEFAULT_PRO_PRICE_DOLLARS;
|
|
136
124
|
const proPriceLabel = env.THUMBGATE_PRO_PRICE_LABEL || DEFAULT_PRO_PRICE_LABEL;
|
|
137
125
|
const gaMeasurementId = normalizeTrackingId(env.THUMBGATE_GA_MEASUREMENT_ID, GA_MEASUREMENT_ID_PATTERN);
|
|
@@ -16,21 +16,30 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
-
const
|
|
19
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
20
20
|
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
// Paths
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
function getHybridPaths(options = {}) {
|
|
26
|
+
const feedbackDir = resolveFeedbackDir({
|
|
27
|
+
cwd: options.cwd,
|
|
28
|
+
env: options.env,
|
|
29
|
+
feedbackDir: options.feedbackDir,
|
|
30
|
+
home: options.home,
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
feedbackDir,
|
|
34
|
+
feedbackLog: path.join(feedbackDir, 'feedback-log.jsonl'),
|
|
35
|
+
inbox: path.join(feedbackDir, 'inbox.jsonl'),
|
|
36
|
+
pendingSync: path.join(feedbackDir, 'pending_cortex_sync.jsonl'),
|
|
37
|
+
attributedFeedback: path.join(feedbackDir, 'attributed-feedback.jsonl'),
|
|
38
|
+
guardArtifact: path.join(feedbackDir, 'pretool-guards.json'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
26
41
|
|
|
27
|
-
const PATHS =
|
|
28
|
-
feedbackLog: path.join(ROOT, '.claude', 'memory', 'feedback', 'feedback-log.jsonl'),
|
|
29
|
-
inbox: path.join(ROOT, '.claude', 'memory', 'feedback', 'inbox.jsonl'),
|
|
30
|
-
pendingSync: path.join(ROOT, '.claude', 'memory', 'feedback', 'pending_cortex_sync.jsonl'),
|
|
31
|
-
attributedFeedback: path.join(ROOT, '.claude', 'memory', 'feedback', 'attributed-feedback.jsonl'),
|
|
32
|
-
guardArtifact: path.join(ROOT, '.claude', 'memory', 'feedback', 'pretool-guards.json'),
|
|
33
|
-
};
|
|
42
|
+
const PATHS = getHybridPaths();
|
|
34
43
|
|
|
35
44
|
// ---------------------------------------------------------------------------
|
|
36
45
|
// Constants
|
|
@@ -193,10 +202,11 @@ function hashText(text) {
|
|
|
193
202
|
*/
|
|
194
203
|
function buildHybridState(opts) {
|
|
195
204
|
const o = opts || {};
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
205
|
+
const paths = getHybridPaths(o);
|
|
206
|
+
const feedbackLogPath = o.feedbackLogPath || process.env.THUMBGATE_FEEDBACK_LOG || paths.feedbackLog;
|
|
207
|
+
const inboxPath = o.inboxPath || process.env.THUMBGATE_FEEDBACK_INBOX || paths.inbox;
|
|
208
|
+
const pendingSyncPath = o.pendingSyncPath || process.env.THUMBGATE_PENDING_SYNC || paths.pendingSync;
|
|
209
|
+
const attributedFeedbackPath = o.attributedFeedbackPath || process.env.THUMBGATE_ATTRIBUTED_FEEDBACK || paths.attributedFeedback;
|
|
200
210
|
|
|
201
211
|
const feedbackEntries = readJsonl(feedbackLogPath);
|
|
202
212
|
const inboxEntries = readJsonl(inboxPath);
|
|
@@ -237,6 +247,21 @@ function buildHybridState(opts) {
|
|
|
237
247
|
entry.context || '',
|
|
238
248
|
entry.whatWentWrong || entry.what_went_wrong || '',
|
|
239
249
|
entry.whatToChange || entry.what_to_change || '',
|
|
250
|
+
entry.failureType || '',
|
|
251
|
+
...(Array.isArray(entry.tags) ? entry.tags : []),
|
|
252
|
+
...(entry.richContext && Array.isArray(entry.richContext.filePaths) ? entry.richContext.filePaths : []),
|
|
253
|
+
...(entry.structuredRule && entry.structuredRule.metadata && Array.isArray(entry.structuredRule.metadata.filesInvolved)
|
|
254
|
+
? entry.structuredRule.metadata.filesInvolved
|
|
255
|
+
: []),
|
|
256
|
+
entry.structuredRule && entry.structuredRule.action ? entry.structuredRule.action.description || '' : '',
|
|
257
|
+
entry.structuredRule && entry.structuredRule.trigger ? entry.structuredRule.trigger.condition || '' : '',
|
|
258
|
+
entry.richContext && entry.richContext.enforcement
|
|
259
|
+
? [
|
|
260
|
+
entry.richContext.enforcement.scopeViolation ? 'scope violation' : '',
|
|
261
|
+
entry.richContext.enforcement.approvalFailure ? 'approval failure' : '',
|
|
262
|
+
entry.richContext.enforcement.protectedFileViolation ? 'protected file violation' : '',
|
|
263
|
+
].join(' ')
|
|
264
|
+
: '',
|
|
240
265
|
].join(' ');
|
|
241
266
|
const norm = normalizePatternText(rawText);
|
|
242
267
|
if (!norm) continue;
|
|
@@ -261,6 +286,20 @@ function buildHybridState(opts) {
|
|
|
261
286
|
const rawText = [
|
|
262
287
|
entry.context || '',
|
|
263
288
|
entry.whatWentWrong || entry.what_went_wrong || '',
|
|
289
|
+
...(Array.isArray(entry.tags) ? entry.tags : []),
|
|
290
|
+
...(entry.richContext && Array.isArray(entry.richContext.filePaths) ? entry.richContext.filePaths : []),
|
|
291
|
+
...(entry.structuredRule && entry.structuredRule.metadata && Array.isArray(entry.structuredRule.metadata.filesInvolved)
|
|
292
|
+
? entry.structuredRule.metadata.filesInvolved
|
|
293
|
+
: []),
|
|
294
|
+
entry.structuredRule && entry.structuredRule.action ? entry.structuredRule.action.description || '' : '',
|
|
295
|
+
entry.structuredRule && entry.structuredRule.trigger ? entry.structuredRule.trigger.condition || '' : '',
|
|
296
|
+
entry.richContext && entry.richContext.enforcement
|
|
297
|
+
? [
|
|
298
|
+
entry.richContext.enforcement.scopeViolation ? 'scope violation' : '',
|
|
299
|
+
entry.richContext.enforcement.approvalFailure ? 'approval failure' : '',
|
|
300
|
+
entry.richContext.enforcement.protectedFileViolation ? 'protected file violation' : '',
|
|
301
|
+
].join(' ')
|
|
302
|
+
: '',
|
|
264
303
|
].join(' ');
|
|
265
304
|
const norm = normalizePatternText(rawText);
|
|
266
305
|
if (!norm) continue;
|
|
@@ -587,7 +626,7 @@ function evaluatePretool(toolName, toolInput, opts) {
|
|
|
587
626
|
const o = opts || {};
|
|
588
627
|
|
|
589
628
|
// Fast path: compiled artifact
|
|
590
|
-
const artifactPath = o.guardArtifactPath || process.env.THUMBGATE_GUARDS_PATH ||
|
|
629
|
+
const artifactPath = o.guardArtifactPath || process.env.THUMBGATE_GUARDS_PATH || getHybridPaths(o).guardArtifact;
|
|
591
630
|
const artifact = readGuardArtifact(artifactPath);
|
|
592
631
|
if (artifact) {
|
|
593
632
|
const result = evaluateCompiledGuards(artifact, toolName, toolInput);
|
|
@@ -668,6 +707,7 @@ module.exports = {
|
|
|
668
707
|
hashText,
|
|
669
708
|
hasTwoKeywordHits,
|
|
670
709
|
readJsonl,
|
|
710
|
+
getHybridPaths,
|
|
671
711
|
PATHS,
|
|
672
712
|
};
|
|
673
713
|
|
package/scripts/install-mcp.js
CHANGED
|
@@ -17,6 +17,7 @@ const path = require('path');
|
|
|
17
17
|
const { resolveMcpEntry } = require('./mcp-config');
|
|
18
18
|
|
|
19
19
|
const MCP_SERVER_KEY = 'thumbgate';
|
|
20
|
+
const LEGACY_MCP_SERVER_KEYS = ['mcp-memory-gateway', 'rlhf'];
|
|
20
21
|
const PKG_ROOT = path.join(__dirname, '..');
|
|
21
22
|
const PKG_VERSION = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version;
|
|
22
23
|
|
|
@@ -80,9 +81,15 @@ function serverConfigMatches(entry, flags = {}) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
function isAlreadyInstalled(settings, flags = {}) {
|
|
84
|
+
const hasLegacyAliases = Boolean(
|
|
85
|
+
settings &&
|
|
86
|
+
settings.mcpServers &&
|
|
87
|
+
LEGACY_MCP_SERVER_KEYS.some((key) => Object.prototype.hasOwnProperty.call(settings.mcpServers, key))
|
|
88
|
+
);
|
|
83
89
|
return !!(
|
|
84
90
|
settings &&
|
|
85
91
|
settings.mcpServers &&
|
|
92
|
+
!hasLegacyAliases &&
|
|
86
93
|
serverConfigMatches(settings.mcpServers[MCP_SERVER_KEY], flags)
|
|
87
94
|
);
|
|
88
95
|
}
|
|
@@ -120,6 +127,11 @@ function installMcp(flags) {
|
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
settings.mcpServers[MCP_SERVER_KEY] = serverConfig;
|
|
130
|
+
for (const legacyKey of LEGACY_MCP_SERVER_KEYS) {
|
|
131
|
+
if (Object.prototype.hasOwnProperty.call(settings.mcpServers, legacyKey)) {
|
|
132
|
+
delete settings.mcpServers[legacyKey];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
123
135
|
|
|
124
136
|
// Ensure parent directory exists
|
|
125
137
|
const dir = path.dirname(settingsPath);
|
|
@@ -142,6 +154,7 @@ function installMcp(flags) {
|
|
|
142
154
|
// Exported for testing
|
|
143
155
|
module.exports = {
|
|
144
156
|
MCP_SERVER_KEY,
|
|
157
|
+
LEGACY_MCP_SERVER_KEYS,
|
|
145
158
|
MCP_SERVER_CONFIG,
|
|
146
159
|
resolveMcpServerConfig,
|
|
147
160
|
resolveSettingsPath,
|
package/scripts/intent-router.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
evaluateDelegation,
|
|
14
14
|
normalizeDelegationMode,
|
|
15
15
|
} = require('./delegation-runtime');
|
|
16
|
+
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
16
17
|
|
|
17
18
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
18
19
|
const DEFAULT_BUNDLE_DIR = path.join(PROJECT_ROOT, 'config', 'policy-bundles');
|
|
@@ -274,8 +275,7 @@ const ACTION_CATEGORY_MAP = {
|
|
|
274
275
|
};
|
|
275
276
|
|
|
276
277
|
function getDefaultModelPath() {
|
|
277
|
-
const feedbackDir =
|
|
278
|
-
|| path.join(PROJECT_ROOT, '.claude', 'memory', 'feedback');
|
|
278
|
+
const feedbackDir = resolveFeedbackDir();
|
|
279
279
|
return path.join(feedbackDir, 'feedback_model.json');
|
|
280
280
|
}
|
|
281
281
|
|
package/scripts/license.js
CHANGED
|
@@ -3,42 +3,71 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const crypto = require('crypto');
|
|
5
5
|
|
|
6
|
-
const LICENSE_PATH = path.join(process.env.HOME || process.env.USERPROFILE || '.', '.thumbgate', 'license.json');
|
|
7
6
|
const VALID_PREFIXES = ['tg_pro_', 'tg_'];
|
|
7
|
+
const LEGACY_COMPATIBLE_KEY = /^[a-z]{4,16}_[a-f0-9]{24,}$/i;
|
|
8
|
+
|
|
9
|
+
function getLicensePath(homeDir = process.env.HOME || process.env.USERPROFILE || '.') {
|
|
10
|
+
return path.join(homeDir, '.thumbgate', 'license.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const LICENSE_PATH = getLicensePath();
|
|
8
14
|
|
|
9
15
|
function isValidKey(key) {
|
|
10
|
-
return
|
|
16
|
+
return Boolean(
|
|
17
|
+
key
|
|
18
|
+
&& (
|
|
19
|
+
VALID_PREFIXES.some((p) => key.startsWith(p))
|
|
20
|
+
|| LEGACY_COMPATIBLE_KEY.test(key)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
11
23
|
}
|
|
12
24
|
|
|
13
|
-
function verifyLicense() {
|
|
14
|
-
const envKey =
|
|
25
|
+
function verifyLicense(options = {}) {
|
|
26
|
+
const envKey = [
|
|
27
|
+
process.env.THUMBGATE_API_KEY,
|
|
28
|
+
process.env.THUMBGATE_PRO_KEY,
|
|
29
|
+
...Object.entries(process.env)
|
|
30
|
+
.filter(([name]) => /(?:_API_KEY|_PRO_KEY)$/.test(name))
|
|
31
|
+
.map(([, value]) => value),
|
|
32
|
+
].find((value) => isValidKey(value));
|
|
15
33
|
if (isValidKey(envKey)) {
|
|
16
34
|
return { valid: true, source: 'env', key: envKey };
|
|
17
35
|
}
|
|
36
|
+
|
|
37
|
+
const licensePath = getLicensePath(options.homeDir);
|
|
18
38
|
try {
|
|
19
|
-
if (fs.existsSync(
|
|
20
|
-
const data = JSON.parse(fs.readFileSync(
|
|
39
|
+
if (fs.existsSync(licensePath)) {
|
|
40
|
+
const data = JSON.parse(fs.readFileSync(licensePath, 'utf8'));
|
|
21
41
|
if (isValidKey(data.key)) {
|
|
22
|
-
return {
|
|
42
|
+
return {
|
|
43
|
+
valid: true,
|
|
44
|
+
source: 'file',
|
|
45
|
+
key: data.key,
|
|
46
|
+
activatedAt: data.activatedAt,
|
|
47
|
+
path: licensePath,
|
|
48
|
+
};
|
|
23
49
|
}
|
|
24
50
|
}
|
|
25
51
|
} catch (_) {}
|
|
52
|
+
|
|
26
53
|
return { valid: false, source: null };
|
|
27
54
|
}
|
|
28
55
|
|
|
29
|
-
function isProLicensed() {
|
|
30
|
-
return verifyLicense().valid;
|
|
56
|
+
function isProLicensed(options) {
|
|
57
|
+
return verifyLicense(options).valid;
|
|
31
58
|
}
|
|
32
59
|
|
|
33
|
-
function activateLicense(key) {
|
|
60
|
+
function activateLicense(key, options = {}) {
|
|
34
61
|
if (!isValidKey(key)) {
|
|
35
62
|
return { success: false, error: 'Invalid key format. Expected tg_... or tg_pro_...' };
|
|
36
63
|
}
|
|
37
|
-
|
|
64
|
+
|
|
65
|
+
const licensePath = getLicensePath(options.homeDir);
|
|
66
|
+
const dir = path.dirname(licensePath);
|
|
38
67
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
39
68
|
const data = { key, activatedAt: new Date().toISOString(), version: require('../package.json').version };
|
|
40
|
-
fs.writeFileSync(
|
|
41
|
-
return { success: true, path:
|
|
69
|
+
fs.writeFileSync(licensePath, JSON.stringify(data, null, 2));
|
|
70
|
+
return { success: true, path: licensePath };
|
|
42
71
|
}
|
|
43
72
|
|
|
44
73
|
function generateLicenseKey(email) {
|
|
@@ -47,4 +76,13 @@ function generateLicenseKey(email) {
|
|
|
47
76
|
return `tg_pro_${hash}`;
|
|
48
77
|
}
|
|
49
78
|
|
|
50
|
-
module.exports = {
|
|
79
|
+
module.exports = {
|
|
80
|
+
verifyLicense,
|
|
81
|
+
isProLicensed,
|
|
82
|
+
activateLicense,
|
|
83
|
+
generateLicenseKey,
|
|
84
|
+
isValidKey,
|
|
85
|
+
VALID_PREFIXES,
|
|
86
|
+
LICENSE_PATH,
|
|
87
|
+
getLicensePath,
|
|
88
|
+
};
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { resolveFeedbackDir: resolveSharedFeedbackDir } = require('./feedback-paths');
|
|
7
8
|
|
|
8
9
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
9
|
-
const DEFAULT_FEEDBACK_DIR =
|
|
10
|
+
const DEFAULT_FEEDBACK_DIR = resolveSharedFeedbackDir();
|
|
10
11
|
const DEFAULT_EMBED_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
11
12
|
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -232,7 +233,7 @@ function recommendInferenceBackend(task = {}, env = process.env) {
|
|
|
232
233
|
}
|
|
233
234
|
|
|
234
235
|
function resolveFeedbackDir(explicitDir) {
|
|
235
|
-
return
|
|
236
|
+
return resolveSharedFeedbackDir({ feedbackDir: explicitDir });
|
|
236
237
|
}
|
|
237
238
|
|
|
238
239
|
function detectHardware(env = process.env) {
|