thumbgate 1.21.1 โ†’ 1.21.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate-marketplace",
3
- "version": "1.21.1",
3
+ "version": "1.21.2",
4
4
  "owner": {
5
5
  "name": "Igor Ganapolsky",
6
6
  "email": "ig5973700@gmail.com"
@@ -14,7 +14,7 @@
14
14
  "source": "npm",
15
15
  "package": "thumbgate"
16
16
  },
17
- "version": "1.21.1",
17
+ "version": "1.21.2",
18
18
  "author": {
19
19
  "name": "Igor Ganapolsky",
20
20
  "email": "ig5973700@gmail.com",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "thumbgate",
3
3
  "description": "One ๐Ÿ‘Ž becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
4
- "version": "1.21.1",
4
+ "version": "1.21.2",
5
5
  "author": {
6
6
  "name": "Igor Ganapolsky",
7
7
  "email": "ig5973700@gmail.com",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.21.1",
3
+ "version": "1.21.2",
4
4
  "description": "ThumbGate โ€” ๐Ÿ‘๐Ÿ‘Ž feedback that teaches your AI agent. Thumbs down a mistake, it never happens again.",
5
5
  "homepage": "https://thumbgate-production.up.railway.app",
6
6
  "transport": "stdio",
@@ -2,13 +2,13 @@
2
2
  "mcpServers": {
3
3
  "thumbgate": {
4
4
  "command": "npx",
5
- "args": ["--yes", "--package", "thumbgate@1.21.1", "thumbgate", "serve"]
5
+ "args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "serve"]
6
6
  }
7
7
  },
8
8
  "hooks": {
9
9
  "preToolUse": {
10
10
  "command": "npx",
11
- "args": ["--yes", "--package", "thumbgate@1.21.1", "thumbgate", "gate-check"]
11
+ "args": ["--yes", "--package", "thumbgate@1.21.2", "thumbgate", "gate-check"]
12
12
  }
13
13
  }
14
14
  }
@@ -216,7 +216,7 @@ const {
216
216
  finalizeSession: finalizeFeedbackSession,
217
217
  } = require('../../scripts/feedback-session');
218
218
 
219
- const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.21.1' };
219
+ const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.21.2' };
220
220
  const COMMERCE_CATEGORIES = [
221
221
  'product_recommendation',
222
222
  'brand_compliance',
@@ -7,7 +7,7 @@
7
7
  "npx",
8
8
  "--yes",
9
9
  "--package",
10
- "thumbgate@1.21.1",
10
+ "thumbgate@1.21.2",
11
11
  "thumbgate",
12
12
  "serve"
13
13
  ],
package/bin/cli.js CHANGED
@@ -23,6 +23,7 @@
23
23
  * npx thumbgate background-governance # background-agent run report + risk check
24
24
  * npx thumbgate cfo # local operational billing summary
25
25
  * npx thumbgate pro # solo dashboard + exports side lane
26
+ * npx thumbgate audit <file> # audit an agent transcript for repeat-mistake token waste
26
27
  */
27
28
 
28
29
  'use strict';
@@ -40,13 +41,6 @@ const {
40
41
  resolveMcpEntry,
41
42
  } = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
42
43
  const { trackEvent } = require(path.join(__dirname, '..', 'scripts', 'cli-telemetry'));
43
- const {
44
- cacheUpdateHookCommand,
45
- preToolHookCommand,
46
- sessionStartHookCommand,
47
- statuslineCommand,
48
- userPromptHookCommand,
49
- } = require(path.join(__dirname, '..', 'scripts', 'hook-runtime'));
50
44
  const {
51
45
  PRO_MONTHLY_PAYMENT_LINK,
52
46
  PRO_PRICE_LABEL,
@@ -395,84 +389,24 @@ function whichExists(cmd) {
395
389
 
396
390
  function setupClaude() {
397
391
  const mcpChanged = mergeMcpJson(path.join(CWD, '.mcp.json'), 'Claude Code', 'project');
392
+ const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
393
+ const hookResult = wireHooks({ agent: 'claude-code' });
398
394
 
399
- // Wire the canonical ThumbGate gate hooks (PreToolUse pre-action check,
400
- // UserPromptSubmit, PostToolUse, SessionStart) through the shared wiring
401
- // path so `init`, `init --agent claude-code`, and `install` all produce the
402
- // same hook set. Before this, `install` shipped a Claude config with no
403
- // PreToolUse gate โ€” the core ThumbGate feature was silently missing.
404
- let wireChanged = false;
405
- try {
406
- const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
407
- const wireResult = wireHooks({ agent: 'claude-code' });
408
- if (wireResult.error) {
409
- console.log(` Claude Code: ${wireResult.error}`);
410
- } else if (wireResult.changed) {
411
- wireChanged = true;
412
- const lifecycles = [...new Set(wireResult.added.map((h) => h.lifecycle))].join(', ');
413
- console.log(` Claude Code: wired gate hooks (${lifecycles})`);
414
- }
415
- } catch (err) {
416
- console.log(` Claude Code: hook wiring skipped (${err.message})`);
417
- }
418
-
419
- // Upsert Stop hook into .claude/settings.json for autonomous self-scoring
420
- const settingsPath = path.join(CWD, '.claude', 'settings.json');
421
- const stopHookCommand = 'bash scripts/hook-stop-self-score.sh';
422
-
423
- let settings = { hooks: {} };
424
- if (fs.existsSync(settingsPath)) {
425
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { /* fresh */ }
426
- }
427
- settings.hooks = settings.hooks || {};
428
-
429
- const stopAlreadyPresent = (settings.hooks.Stop || [])
430
- .some(entry => (entry.hooks || []).some(h => h.command === stopHookCommand));
431
-
432
- let hooksChanged = false;
433
- if (!stopAlreadyPresent) {
434
- settings.hooks.Stop = settings.hooks.Stop || [];
435
- settings.hooks.Stop.push({ hooks: [{ type: 'command', command: stopHookCommand }] });
436
- hooksChanged = true;
437
- console.log(' Claude Code: installed Stop hook');
395
+ if (hookResult.error) {
396
+ console.log(` Claude Code hooks: ${hookResult.error}`);
397
+ return mcpChanged;
438
398
  }
439
399
 
440
- // Upsert PostToolUse hook for ThumbGate statusline cache updates
441
- const cacheHookCommand = cacheUpdateHookCommand();
442
- const originalPostToolUseCount = (settings.hooks.PostToolUse || []).length;
443
- settings.hooks.PostToolUse = (settings.hooks.PostToolUse || []).filter(
444
- (entry) => !(entry.hooks || []).some((h) => h.command && h.command !== cacheHookCommand && /(hook-thumbgate-cache-updater|cache-update\b)/.test(h.command))
445
- );
446
- if (settings.hooks.PostToolUse.length !== originalPostToolUseCount) {
447
- hooksChanged = true;
448
- }
449
- const cacheAlreadyPresent = (settings.hooks.PostToolUse || [])
450
- .some(entry => (entry.hooks || []).some(h => h.command === cacheHookCommand || (h.command && h.command.includes('cache-update'))));
451
-
452
- if (!cacheAlreadyPresent) {
453
- settings.hooks.PostToolUse = settings.hooks.PostToolUse || [];
454
- settings.hooks.PostToolUse.push({
455
- matcher: 'mcp__thumbgate__feedback_stats|mcp__thumbgate__dashboard',
456
- hooks: [{ type: 'command', command: cacheHookCommand }]
457
- });
458
- hooksChanged = true;
459
- console.log(' Claude Code: installed ThumbGate cache updater hook');
460
- }
461
-
462
- // Upsert statusLine for ThumbGate feedback display
463
- const statuslineScript = statuslineCommand();
464
- if (!settings.statusLine || settings.statusLine.command !== statuslineScript) {
465
- settings.statusLine = { type: 'command', command: statuslineScript };
466
- hooksChanged = true;
467
- console.log(' Claude Code: installed ThumbGate status line');
468
- }
469
-
470
- if (hooksChanged) {
471
- fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
472
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
400
+ if (!hookResult.changed) {
401
+ console.log(` Claude Code hooks: already configured at ${hookResult.settingsPath}`);
402
+ } else {
403
+ for (const h of hookResult.added) {
404
+ console.log(` Claude Code: wired ${h.lifecycle} hook`);
405
+ }
406
+ console.log(` Claude Code settings: ${hookResult.settingsPath}`);
473
407
  }
474
408
 
475
- return mcpChanged || hooksChanged || wireChanged;
409
+ return mcpChanged || hookResult.changed;
476
410
  }
477
411
 
478
412
  function setupCodex() {
@@ -744,7 +678,11 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
744
678
  let configured = 0;
745
679
 
746
680
  const platforms = [
747
- { name: 'Claude Code', detect: [() => whichExists('claude'), () => fs.existsSync(path.join(HOME, '.claude'))], setup: setupClaude },
681
+ { name: 'Claude Code', detect: [
682
+ () => whichExists('claude'),
683
+ () => fs.existsSync(path.join(HOME, '.claude')),
684
+ () => fs.existsSync(path.join(CWD, '.claude')),
685
+ ], setup: setupClaude },
748
686
  { name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
749
687
  { name: 'Gemini', detect: [() => whichExists('gemini'), () => fs.existsSync(path.join(HOME, '.gemini'))], setup: setupGemini },
750
688
  { name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
@@ -2884,6 +2822,29 @@ switch (COMMAND) {
2884
2822
  }
2885
2823
  break;
2886
2824
  }
2825
+ case 'audit': {
2826
+ const auditFile = process.argv[3];
2827
+ if (!auditFile) {
2828
+ console.error('Usage: npx thumbgate audit <path-to-transcript.txt>');
2829
+ process.exit(1);
2830
+ }
2831
+ const { runAudit } = require(path.join(PKG_ROOT, 'scripts', 'audit'));
2832
+ const { results, totalWaste, error } = runAudit(auditFile);
2833
+ if (error) {
2834
+ console.error(error);
2835
+ process.exit(1);
2836
+ }
2837
+ console.log('\n๐Ÿ” AI Bill Audit Results\n');
2838
+ if (results.length === 0) {
2839
+ console.log('โœ… No repeat-offender patterns found. Your sessions are efficient!');
2840
+ } else {
2841
+ console.table(results);
2842
+ console.log('\n๐Ÿ’ฐ Total estimated monthly waste: $' + totalWaste);
2843
+ console.log('\nBlock these mistakes permanently with ThumbGate Pro:');
2844
+ console.log(PRO_CHECKOUT_URL);
2845
+ }
2846
+ break;
2847
+ }
2887
2848
  case 'dashboard':
2888
2849
  dashboard();
2889
2850
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thumbgate",
3
- "version": "1.21.1",
3
+ "version": "1.21.2",
4
4
  "description": "ThumbGate self-improving agent governance: thumbs-up/down turns every mistake into a prevention rule and blocks repeat patterns. 33 pre-action checks, budget enforcement, and self-protection for Claude Code, Cursor, Codex, Gemini CLI, and Amp.",
5
5
  "homepage": "https://thumbgate-production.up.railway.app",
6
6
  "repository": {
@@ -31,6 +31,7 @@
31
31
  "scripts/analytics-window.js",
32
32
  "scripts/async-job-runner.js",
33
33
  "scripts/audit-trail.js",
34
+ "scripts/audit.js",
34
35
  "scripts/auto-promote-gates.js",
35
36
  "scripts/auto-wire-hooks.js",
36
37
  "scripts/autoresearch-runner.js",
@@ -639,10 +640,6 @@
639
640
  "test:competitive-positioning-marketing": "node --test tests/competitive-positioning-marketing.test.js tests/knowledge-graph-guardrails.test.js tests/supply-chain-guardrails.test.js",
640
641
  "test:medium-weekly": "node --test tests/medium-weekly.test.js",
641
642
  "test:dashboard-deeplink-e2e": "node --test tests/dashboard-deeplink-e2e.test.js",
642
- "test:e2e:playwright": "playwright test",
643
- "test:e2e:playwright:headed": "playwright test --headed",
644
- "test:e2e:playwright:ui": "playwright test --ui",
645
- "test:e2e:playwright:report": "playwright show-report",
646
643
  "test:public-package-parity": "node --test tests/public-package-parity.test.js",
647
644
  "prepare": "bash bin/install-hooks.sh >/dev/null 2>&1 || true",
648
645
  "install:hooks": "bash bin/install-hooks.sh",
@@ -734,7 +731,6 @@
734
731
  "devDependencies": {
735
732
  "@changesets/changelog-github": "^0.7.0",
736
733
  "@changesets/cli": "^2.31.0",
737
- "@playwright/test": "^1.60.0",
738
734
  "c8": "^11.0.0",
739
735
  "undici": "^8.2.0"
740
736
  }
@@ -247,9 +247,9 @@
247
247
 
248
248
  <!-- STATS -->
249
249
  <div class="stats-grid" id="statsGrid">
250
- <a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons?signal=all" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view all feedback โ†’ Lessons page"><div class="stat-label">Total Feedback</div><div class="stat-value cyan" id="statTotal">โ€”</div></a>
251
- <a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=up" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view positive feedback โ†’ Lessons page"><div class="stat-label">๐Ÿ‘ Positive</div><div class="stat-value green" id="statPositive">โ€”</div></a>
252
- <a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=down" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view negative feedback โ†’ Lessons page"><div class="stat-label">๐Ÿ‘Ž Negative</div><div class="stat-value red" id="statNegative">โ€”</div></a>
250
+ <a class="stat-card" data-card-action="all" onclick="selectCard(this,'all')" href="/lessons" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view all feedback โ†’ Lessons page"><div class="stat-label">Total Feedback</div><div class="stat-value cyan" id="statTotal">โ€”</div></a>
251
+ <a class="stat-card" data-card-action="up" onclick="selectCard(this,'up')" href="/lessons?signal=positive" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view positive feedback โ†’ Lessons page"><div class="stat-label">๐Ÿ‘ Positive</div><div class="stat-value green" id="statPositive">โ€”</div></a>
252
+ <a class="stat-card" data-card-action="down" onclick="selectCard(this,'down')" href="/lessons?signal=negative" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view negative feedback โ†’ Lessons page"><div class="stat-label">๐Ÿ‘Ž Negative</div><div class="stat-value red" id="statNegative">โ€”</div></a>
253
253
  <a class="stat-card" data-card-action="gates" onclick="selectCard(this,'gates');return false;" href="#" style="cursor:pointer;text-decoration:none;color:inherit;display:block;" title="Click to view active checks"><div class="stat-label">Active Gates</div><div class="stat-value cyan" id="statGates">โ€”</div></a>
254
254
  </div>
255
255
 
package/public/index.html CHANGED
@@ -19,7 +19,7 @@ __GOOGLE_SITE_VERIFICATION_META__
19
19
  <meta property="og:image" content="https://thumbgate-production.up.railway.app/og.png">
20
20
  <meta name="twitter:card" content="summary_large_image">
21
21
  <meta name="twitter:image" content="https://thumbgate-production.up.railway.app/og.png">
22
- <meta name="thumbgate-version" content="1.21.1">
22
+ <meta name="thumbgate-version" content="1.21.2">
23
23
  <meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agent enforcement layer, save LLM tokens, reduce Claude API cost, reduce OpenAI cost, AI agent token savings, prevent LLM retries, prevent hallucination retries, stop AI token waste, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
24
24
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
25
25
 
@@ -1492,7 +1492,7 @@ __GA_BOOTSTRAP__
1492
1492
  <a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
1493
1493
  <a href="/blog">Blog</a>
1494
1494
  </div>
1495
- <span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.21.1</span>
1495
+ <span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.21.2</span>
1496
1496
  </div>
1497
1497
  </footer>
1498
1498
 
@@ -922,28 +922,6 @@ async function loadLive() {
922
922
  }
923
923
 
924
924
  loadLive().then(function() {
925
- // Handle ?signal= query param from dashboard stat-card navigation.
926
- // Vocabulary: 'up' | 'down' | 'all' (canonical). Also accepts the legacy
927
- // 'positive' | 'negative' aliases the dashboard once emitted.
928
- var qsSignal = new URLSearchParams(window.location.search).get('signal');
929
- if (qsSignal) {
930
- var signalMap = { positive: 'up', negative: 'down', up: 'up', down: 'down', all: 'all' };
931
- var mapped = signalMap[qsSignal];
932
- if (mapped) {
933
- switchTab('timeline');
934
- filterTimeline(mapped, null);
935
- var filterBtns = document.querySelectorAll('#tab-timeline .filter-btn');
936
- filterBtns.forEach(function(b) {
937
- var label = b.textContent.trim().toLowerCase();
938
- var match = (mapped === 'all' && label === 'all') ||
939
- (mapped === 'up' && (label.indexOf('๐Ÿ‘') !== -1 || label.indexOf('positive') !== -1 || label === 'up')) ||
940
- (mapped === 'down' && (label.indexOf('๐Ÿ‘Ž') !== -1 || label.indexOf('negative') !== -1 || label === 'down'));
941
- b.classList.toggle('active', match);
942
- });
943
- return;
944
- }
945
- }
946
-
947
925
  // Default: highlight Active Rules card on page load
948
926
  highlightCard(0);
949
927
 
@@ -25,7 +25,7 @@
25
25
  "alternateName": "thumbgate",
26
26
  "applicationCategory": "DeveloperApplication",
27
27
  "operatingSystem": "Cross-platform, Node.js >=18.18.0",
28
- "softwareVersion": "1.21.1",
28
+ "softwareVersion": "1.21.2",
29
29
  "url": "https://thumbgate-production.up.railway.app/numbers",
30
30
  "dateModified": "2026-05-07",
31
31
  "creator": {
@@ -202,7 +202,7 @@
202
202
  <main class="container">
203
203
  <h1>The Numbers</h1>
204
204
  <p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
205
- <div class="freshness">Updated: 2026-05-07 ยท Version 1.21.1</div>
205
+ <div class="freshness">Updated: 2026-05-07 ยท Version 1.21.2</div>
206
206
  <div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
207
207
 
208
208
  <h2>Gate enforcement</h2>
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * scripts/audit.js
5
+ *
6
+ * Heuristic-based AI bill auditor. Finds repeated mistakes in agent transcripts.
7
+ */
8
+
9
+ const fs = require('fs');
10
+
11
+ const PATTERNS = [
12
+ {
13
+ id: 'force-push-retry',
14
+ name: 'git push --force after correction',
15
+ regex: /git\s+push.*--force/gi,
16
+ tokenEstimate: 6000,
17
+ costPerRepeat: 0.44,
18
+ why: 'Full diff context reload on error.'
19
+ },
20
+ {
21
+ id: 'import-loop',
22
+ name: 'Hallucinated import retry',
23
+ regex: /(Cannot find module|Module not found|import .* from .*error)/gi,
24
+ tokenEstimate: 4000,
25
+ costPerRepeat: 0.12,
26
+ why: 'Re-indexing and path searching.'
27
+ },
28
+ {
29
+ id: 'apology-loop',
30
+ name: '"I apologize" retry cycle',
31
+ regex: /(I apologize|Let me try a different approach|I will now attempt)/gi,
32
+ tokenEstimate: 5000,
33
+ costPerRepeat: 0.15,
34
+ why: 'Reasoning chain reset.'
35
+ }
36
+ ];
37
+
38
+ function runAudit(filePath) {
39
+ if (!fs.existsSync(filePath)) {
40
+ return { error: 'File not found: ' + filePath };
41
+ }
42
+
43
+ const content = fs.readFileSync(filePath, 'utf8');
44
+ const results = [];
45
+ let totalWaste = 0;
46
+
47
+ PATTERNS.forEach(p => {
48
+ const matches = (content.match(p.regex) || []).length;
49
+ if (matches > 1) { // It's only a "repeat" if it happens more than once
50
+ const repeats = matches - 1;
51
+ const waste = repeats * p.costPerRepeat;
52
+ totalWaste += waste;
53
+ results.push({
54
+ pattern: p.name,
55
+ occurrences: repeats,
56
+ waste: waste.toFixed(2),
57
+ why: p.why
58
+ });
59
+ }
60
+ });
61
+
62
+ return { results, totalWaste: totalWaste.toFixed(2) };
63
+ }
64
+
65
+ module.exports = { runAudit, PATTERNS };
@@ -525,6 +525,14 @@ const CLI_COMMANDS = [
525
525
  { name: 'json', type: 'boolean', description: 'Output as JSON' },
526
526
  ],
527
527
  },
528
+ {
529
+ name: 'audit',
530
+ description: 'Audit an agent transcript for repeat-mistake patterns and estimated token waste',
531
+ group: 'ops',
532
+ flags: [
533
+ { name: 'file', type: 'string', description: 'Path to the agent transcript to audit' },
534
+ ],
535
+ },
528
536
  {
529
537
  name: 'init',
530
538
  description: 'Scaffold .thumbgate/ config and wire agent hooks',
@@ -33,34 +33,34 @@ function publishedHookCommandsAvailable(version) {
33
33
  return available;
34
34
  }
35
35
 
36
- function resolveCliBaseCommand() {
36
+ function resolveCliCommand(subcommand) {
37
37
  const version = packageVersion();
38
38
  if (publishedHookCommandsAvailable(version)) {
39
- return publishedCliShellCommand(version);
39
+ return publishedCliShellCommand(version, [subcommand]);
40
40
  }
41
41
  if (isSourceCheckout(PKG_ROOT)) {
42
- return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
42
+ return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))} ${subcommand}`;
43
43
  }
44
- return publishedCliShellCommand(version);
44
+ return publishedCliShellCommand(version, [subcommand]);
45
45
  }
46
46
 
47
- function resolveCodexCliBaseCommand() {
47
+ function resolveCodexCliCommand(subcommand) {
48
48
  const version = packageVersion();
49
49
  if (publishedHookCommandsAvailable(version)) {
50
- return publishedCliShellCommand('latest', [], { preferInstalled: false });
50
+ return publishedCliShellCommand('latest', [subcommand], { preferInstalled: false });
51
51
  }
52
52
  if (isSourceCheckout(PKG_ROOT)) {
53
- return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))}`;
53
+ return `node ${shellQuote(path.join(PKG_ROOT, 'bin', 'cli.js'))} ${subcommand}`;
54
54
  }
55
- return publishedCliShellCommand('latest', [], { preferInstalled: false });
55
+ return publishedCliShellCommand('latest', [subcommand], { preferInstalled: false });
56
56
  }
57
57
 
58
58
  function buildPortableHookCommand(subcommand) {
59
- return `${resolveCliBaseCommand()} ${subcommand}`;
59
+ return resolveCliCommand(subcommand);
60
60
  }
61
61
 
62
62
  function buildCodexPortableHookCommand(subcommand) {
63
- return `${resolveCodexCliBaseCommand()} ${subcommand}`;
63
+ return resolveCodexCliCommand(subcommand);
64
64
  }
65
65
 
66
66
  function preToolHookCommand() {
@@ -115,8 +115,8 @@ module.exports = {
115
115
  packageVersion,
116
116
  publishedHookCommandsAvailable,
117
117
  preToolHookCommand,
118
- resolveCodexCliBaseCommand,
119
- resolveCliBaseCommand,
118
+ resolveCodexCliCommand,
119
+ resolveCliCommand,
120
120
  sessionStartHookCommand,
121
121
  statuslineCommand,
122
122
  userPromptHookCommand,
@@ -7,6 +7,7 @@
7
7
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
8
8
  case "$SCRIPT_DIR" in *[!a-zA-Z0-9/_.-]*) echo "ThumbGate: invalid script path"; exit 1;; esac
9
9
  LOCAL_API_ORIGIN="${THUMBGATE_LOCAL_API_ORIGIN:-http://localhost:3456}"
10
+ STATUSLINE_VERBOSE="${THUMBGATE_STATUSLINE_VERBOSE:-0}"
10
11
 
11
12
  # โ”€โ”€ Parse Claude Code session JSON from stdin โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
12
13
  eval "$(cat | jq -r '
@@ -92,17 +93,19 @@ fi
92
93
  LINK_STATE="offline"
93
94
  UP_URL=""; DOWN_URL=""; DASHBOARD_URL=""; LESSONS_URL=""
94
95
  DASHBOARD_LABEL="Dashboard"; LESSONS_LABEL="Lessons"
95
- _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
96
- if [ -n "$_LINKS_JSON" ]; then
97
- eval "$(echo "$_LINKS_JSON" | jq -r '
98
- @sh "LINK_STATE=\(.state // "offline")",
99
- @sh "UP_URL=\(.upUrl // "")",
100
- @sh "DOWN_URL=\(.downUrl // "")",
101
- @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
102
- @sh "LESSONS_URL=\(.lessonsUrl // "")",
103
- @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
104
- @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
105
- ' 2>/dev/null)"
96
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
97
+ _LINKS_JSON=$(node "${SCRIPT_DIR}/statusline-links.js" 2>/dev/null)
98
+ if [ -n "$_LINKS_JSON" ]; then
99
+ eval "$(echo "$_LINKS_JSON" | jq -r '
100
+ @sh "LINK_STATE=\(.state // "offline")",
101
+ @sh "UP_URL=\(.upUrl // "")",
102
+ @sh "DOWN_URL=\(.downUrl // "")",
103
+ @sh "DASHBOARD_URL=\(.dashboardUrl // "")",
104
+ @sh "LESSONS_URL=\(.lessonsUrl // "")",
105
+ @sh "DASHBOARD_LABEL=\(.dashboardLabel // "Dashboard")",
106
+ @sh "LESSONS_LABEL=\(.lessonsLabel // "Lessons")"
107
+ ' 2>/dev/null)"
108
+ fi
106
109
  fi
107
110
 
108
111
  # โ”€โ”€ ThumbGate package metadata โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -117,13 +120,15 @@ fi
117
120
 
118
121
  # โ”€โ”€ Repo context (branch / work item / PR) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
119
122
  BRANCH_NAME=""; WORK_ITEM_LABEL=""; PR_LABEL=""
120
- _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
121
- if [[ -n "$_CONTEXT_JSON" ]]; then
122
- eval "$(echo "$_CONTEXT_JSON" | jq -r '
123
- @sh "BRANCH_NAME=\(.branchName // "")",
124
- @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
125
- @sh "PR_LABEL=\(.prLabel // "")"
126
- ' 2>/dev/null)"
123
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
124
+ _CONTEXT_JSON=$(node "${SCRIPT_DIR}/statusline-context.js" 2>/dev/null)
125
+ if [[ -n "$_CONTEXT_JSON" ]]; then
126
+ eval "$(echo "$_CONTEXT_JSON" | jq -r '
127
+ @sh "BRANCH_NAME=\(.branchName // "")",
128
+ @sh "WORK_ITEM_LABEL=\(.workItemLabel // "")",
129
+ @sh "PR_LABEL=\(.prLabel // "")"
130
+ ' 2>/dev/null)"
131
+ fi
127
132
  fi
128
133
 
129
134
  # โ”€โ”€ Control Tower stats โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -139,14 +144,16 @@ fi
139
144
 
140
145
  # โ”€โ”€ Latest lesson (data available for extensions; not rendered in statusbar) โ”€โ”€
141
146
  LESSON_TEXT=""; LESSON_ID=""; LESSON_LABEL=""; LESSON_LINK=""
142
- _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
143
- if [[ -n "$_LESSON_JSON" ]]; then
144
- eval "$(echo "$_LESSON_JSON" | jq -r '
145
- @sh "LESSON_TEXT=\(.text // "")",
146
- @sh "LESSON_ID=\(.lessonId // "")",
147
- @sh "LESSON_LABEL=\(.label // "")",
148
- @sh "LESSON_LINK=\(.link // "")"
149
- ' 2>/dev/null)"
147
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
148
+ _LESSON_JSON=$(node "${SCRIPT_DIR}/statusline-lesson.js" 2>/dev/null)
149
+ if [[ -n "$_LESSON_JSON" ]]; then
150
+ eval "$(echo "$_LESSON_JSON" | jq -r '
151
+ @sh "LESSON_TEXT=\(.text // "")",
152
+ @sh "LESSON_ID=\(.lessonId // "")",
153
+ @sh "LESSON_LABEL=\(.label // "")",
154
+ @sh "LESSON_LINK=\(.link // "")"
155
+ ' 2>/dev/null)"
156
+ fi
150
157
  fi
151
158
 
152
159
  # โ”€โ”€ Colors โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -199,8 +206,10 @@ LINE="${LINE:+${LINE} ยท }ThumbGate v${TG_VERSION} ยท ${TG_TIER}"
199
206
  if [[ "$UP" = "0" && "$DOWN" = "0" ]]; then
200
207
  LINE="${D}${LINE}${RST} ยท no feedback yet"
201
208
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} ยท ${D}${PR_LABEL}${RST}"
202
- LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
203
- [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
209
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
210
+ LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
211
+ [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
212
+ fi
204
213
  printf '%b\n' "$LINE"
205
214
  else
206
215
  LINE="${LINE} ยท ${G}${BD}${UP}${RST}${UP_LINK} ${R}${BD}${DOWN}${RST}${DOWN_LINK} ${ARROW}"
@@ -210,8 +219,10 @@ else
210
219
  [[ "${AT_RISK:-0}" -gt 0 ]] && LINE="${LINE} ${R}${AT_RISK}โš ${RST}"
211
220
  [[ "${ANOMALIES:-0}" -gt 0 ]] && LINE="${LINE} ${R}${ANOMALIES}โ˜ ${RST}"
212
221
  [[ -n "$PR_LABEL" ]] && LINE="${LINE} ยท ${D}${PR_LABEL}${RST}"
213
- LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
214
- [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
222
+ if [[ "$STATUSLINE_VERBOSE" = "1" ]]; then
223
+ LINE="${LINE} ยท ${C}${DASHBOARD_LINK}${RST} ยท ${M}${LESSONS_LINK}${RST}"
224
+ [[ -n "$LATEST_LESSON_LINK" ]] && LINE="${LINE} ยท ${D}${LATEST_LESSON_LINK}${RST}"
225
+ fi
215
226
 
216
227
  printf '%b\n' "$LINE"
217
228
  fi
@@ -1178,15 +1178,10 @@ function chooseDecision({ riskScore, integrity, memoryGuard, learnedPolicy, blas
1178
1178
  if (lowRiskHandoff) {
1179
1179
  return 'allow';
1180
1180
  }
1181
- // Background customer-system actions checkpoint (warn), never hard-deny.
1182
- // The checkpoint IS the mitigation โ€” blocking outright prevents legitimate work.
1183
- if (backgroundAgent && customerSystemAction) {
1184
- return 'warn';
1185
- }
1186
1181
  if (destructiveBypass || learnedHardStop || repeatedHighBlast || (hasOperationalBlockers && riskScore >= 0.72) || riskScore >= 0.86) {
1187
1182
  return 'deny';
1188
1183
  }
1189
- if (economicAction || (backgroundAgent && riskScore >= 0.3)) {
1184
+ if (economicAction || (backgroundAgent && customerSystemAction) || (backgroundAgent && riskScore >= 0.3)) {
1190
1185
  return 'warn';
1191
1186
  }
1192
1187
  if ((workflowControl && workflowControl.mode === 'warn') || (costControl && costControl.mode === 'warn') || riskScore >= 0.45 || (learnedWarning && riskScore >= 0.3) || (learnedRecall && riskScore >= 0.34)) {