thumbgate 1.37.2 → 1.37.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.agents/skills/article-syndication-pipeline/SKILL.md +37 -0
  2. package/.agents/skills/ci-gha-buildkite-patterns-not-clone/SKILL.md +72 -0
  3. package/.agents/skills/comet-session-logger/SKILL.md +59 -0
  4. package/.agents/skills/datadog-llm-obs-compare-not-clone/SKILL.md +75 -0
  5. package/.agents/skills/deeppattern-discipline-honesty-not-clone/SKILL.md +73 -0
  6. package/.agents/skills/thumbgate-board-loop/SKILL.md +72 -0
  7. package/.agents/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
  8. package/.agents/skills/typesafe-typed-questions-not-clone/SKILL.md +7 -3
  9. package/.claude-plugin/plugin.json +1 -1
  10. package/.well-known/mcp/server-card.json +1 -1
  11. package/adapters/claude/.mcp.json +2 -2
  12. package/adapters/forge/forge.yaml +3 -3
  13. package/adapters/future-agi/.mcp.json +1 -1
  14. package/adapters/future-agi/config.toml +1 -1
  15. package/adapters/future-agi/opencode.json +1 -1
  16. package/adapters/herdr/herdr-plugin.toml +2 -2
  17. package/adapters/mcp/server-stdio.js +1 -1
  18. package/adapters/opencode/opencode.json +1 -1
  19. package/bin/cli.js +132 -4
  20. package/config/gate-templates.json +48 -0
  21. package/package.json +36 -9
  22. package/public/blog/2026-09-23-datadog-llm-obs-four-practices.html +42 -0
  23. package/public/index.html +2 -2
  24. package/public/install.html +7 -7
  25. package/public/numbers.html +2 -2
  26. package/scripts/ci-gha-buildkite-patterns.js +393 -0
  27. package/scripts/cli-schema.js +51 -1
  28. package/scripts/deeppattern-discipline-honesty.js +431 -0
  29. package/scripts/install-thumbgate-daily-publish-launchd.sh +83 -0
  30. package/scripts/llm-obs-honesty.js +478 -0
  31. package/scripts/thumbgate-board-loop.js +432 -0
  32. package/scripts/thumbgate-daily-discoveries-publish.js +413 -0
  33. package/scripts/thumbgate-daily-discoveries-publish.sh +18 -0
  34. package/scripts/typesafe-typed-questions.js +332 -7
  35. package/server.json +2 -2
  36. package/skills/article-syndication-pipeline/SKILL.md +37 -0
  37. package/skills/comet-session-logger/SKILL.md +59 -0
  38. package/skills/thumbgate-daily-discoveries/SKILL.md +57 -0
  39. package/src/index.js +10 -0
  40. package/src/integrations/ideabrowser-connector.js +190 -0
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * ideabrowser-connector.js — IdeaBrowser Agent Connector & Governance Diode.
5
+ *
6
+ * Implements a hardened IdeaBrowser connector for ThumbGate without credential
7
+ * leakage, classifying all actions into read-only or mutating tiers. Closes #3823.
8
+ *
9
+ * Security & Governance Invariants:
10
+ * 1. Zero Plaintext Credentials:
11
+ * - Connector tokens/secrets must never be stored in tracked repository files.
12
+ * - Resolve credentials via authenticated environment variables (IDEABROWSER_API_KEY)
13
+ * or macOS Keychain lookup.
14
+ * 2. Strict Tool Classification:
15
+ * - read-only: Information gathering, DOM inspection, status query, screenshot, URL retrieval. Allowed by default.
16
+ * - mutating: Form submission, button click, file upload, state mutation, purchasing, external navigation.
17
+ * - Fail-Closed on Provider Drift: Unknown tools default immediately to mutating.
18
+ * 3. Pre-Action Interdiction:
19
+ * - Emits an interdiction event before executing any mutating tool.
20
+ * - Hard-blocks unauthorized mutating operations when fail-closed mode is set.
21
+ */
22
+
23
+ const { EventEmitter } = require('node:events');
24
+ const { redactSecrets, redactSecretsDeep } = require('../../scripts/secret-redaction');
25
+
26
+ const ToolTier = Object.freeze({
27
+ READ_ONLY: 'read-only',
28
+ MUTATING: 'mutating',
29
+ });
30
+
31
+ const DEFAULT_READ_ONLY_TOOLS = new Set([
32
+ 'get_page_content',
33
+ 'get_dom_tree',
34
+ 'get_url',
35
+ 'inspect_element',
36
+ 'take_screenshot',
37
+ 'get_status',
38
+ 'search_page',
39
+ 'list_tabs',
40
+ 'read_console_logs',
41
+ ]);
42
+
43
+ const DEFAULT_MUTATING_TOOLS = new Set([
44
+ 'click_element',
45
+ 'fill_form',
46
+ 'submit_form',
47
+ 'navigate_url',
48
+ 'upload_file',
49
+ 'execute_script',
50
+ 'purchase_item',
51
+ 'close_tab',
52
+ 'press_key',
53
+ ]);
54
+
55
+ class IdeaBrowserConnector extends EventEmitter {
56
+ constructor(options = {}) {
57
+ super();
58
+ this.apiKey = options.apiKey || process.env.IDEABROWSER_API_KEY || null;
59
+ this.baseUrl = options.baseUrl || 'https://api.ideabrowser.com/v1';
60
+ this.failClosed = options.failClosed !== false;
61
+ this.allowedDomains = Array.isArray(options.allowedDomains)
62
+ ? new Set(options.allowedDomains)
63
+ : null;
64
+ this.toolRegistry = new Map();
65
+
66
+ // Seed default known tools
67
+ for (const tool of DEFAULT_READ_ONLY_TOOLS) {
68
+ this.toolRegistry.set(tool, {
69
+ tier: ToolTier.READ_ONLY,
70
+ handler: options.handlers?.[tool] || (async (params) => ({ tool, params, status: 'ok' })),
71
+ });
72
+ }
73
+
74
+ for (const tool of DEFAULT_MUTATING_TOOLS) {
75
+ this.toolRegistry.set(tool, {
76
+ tier: ToolTier.MUTATING,
77
+ handler: options.handlers?.[tool] || (async (params) => ({ tool, params, status: 'ok' })),
78
+ });
79
+ }
80
+ }
81
+
82
+ getCredentials() {
83
+ if (this.apiKey) {
84
+ return {
85
+ hasKey: true,
86
+ source: 'env_or_options',
87
+ maskedKey: redactSecrets(this.apiKey),
88
+ };
89
+ }
90
+ return {
91
+ hasKey: false,
92
+ source: 'none',
93
+ maskedKey: null,
94
+ };
95
+ }
96
+
97
+ registerTool(name, handler, tier = ToolTier.MUTATING) {
98
+ if (!name || typeof name !== 'string') {
99
+ throw new Error('Tool name must be a non-empty string.');
100
+ }
101
+ if (typeof handler !== 'function') {
102
+ throw new Error('Tool handler must be a function.');
103
+ }
104
+ const resolvedTier = tier === ToolTier.READ_ONLY ? ToolTier.READ_ONLY : ToolTier.MUTATING;
105
+ this.toolRegistry.set(name, { handler, tier: resolvedTier });
106
+ }
107
+
108
+ classifyTool(name) {
109
+ const entry = this.toolRegistry.get(name);
110
+ if (entry) {
111
+ return entry.tier;
112
+ }
113
+ // Fail closed on drift: Unknown tools are classified as mutating
114
+ return this.failClosed ? ToolTier.MUTATING : ToolTier.READ_ONLY;
115
+ }
116
+
117
+ isAllowedDomain(urlOrDomain) {
118
+ if (!this.allowedDomains) {
119
+ return true;
120
+ }
121
+ if (this.allowedDomains.size === 0) return false;
122
+ if (!urlOrDomain) return false;
123
+ try {
124
+ const parsed = urlOrDomain.startsWith('http')
125
+ ? new URL(urlOrDomain)
126
+ : new URL(`https://${urlOrDomain}`);
127
+ return this.allowedDomains.has(parsed.hostname);
128
+ } catch {
129
+ return this.allowedDomains.has(urlOrDomain);
130
+ }
131
+ }
132
+
133
+ async executeTool(name, params = {}, context = {}) {
134
+ const tier = this.classifyTool(name);
135
+ const entry = this.toolRegistry.get(name);
136
+
137
+ if (tier === ToolTier.MUTATING) {
138
+ const targetUrl = params.url || params.target || context.url;
139
+ if (targetUrl && !this.isAllowedDomain(targetUrl)) {
140
+ let domainOnly = targetUrl;
141
+ try {
142
+ const parsed = targetUrl.startsWith('http')
143
+ ? new URL(targetUrl)
144
+ : new URL(`https://${targetUrl}`);
145
+ domainOnly = parsed.origin;
146
+ } catch {
147
+ domainOnly = redactSecrets(targetUrl);
148
+ }
149
+ const error = new Error(`Domain not allowed for mutating tool "${name}": ${domainOnly}`);
150
+ error.code = 'DOMAIN_INTERDICTED';
151
+ this.emit('interdict', {
152
+ name,
153
+ params: redactSecretsDeep(params),
154
+ context: redactSecretsDeep(context),
155
+ reason: redactSecrets(error.message),
156
+ });
157
+ if (this.failClosed) {
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ this.emit('interdict', {
163
+ name,
164
+ params: redactSecretsDeep(params),
165
+ context: redactSecretsDeep(context),
166
+ });
167
+
168
+ if (context.blocked === true) {
169
+ if (this.failClosed) {
170
+ const error = new Error(`Execution of mutating tool "${name}" blocked by pre-action gate.`);
171
+ error.code = 'GATE_INTERDICTED';
172
+ throw error;
173
+ }
174
+ }
175
+ }
176
+
177
+ if (!entry) {
178
+ throw new Error(`IdeaBrowser tool "${name}" not found.`);
179
+ }
180
+
181
+ return await entry.handler(params);
182
+ }
183
+ }
184
+
185
+ module.exports = {
186
+ ToolTier,
187
+ IdeaBrowserConnector,
188
+ DEFAULT_READ_ONLY_TOOLS,
189
+ DEFAULT_MUTATING_TOOLS,
190
+ };