thumbgate 1.2.0 → 1.4.0
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 +4 -4
- package/.claude-plugin/marketplace.json +32 -13
- package/.claude-plugin/plugin.json +15 -2
- package/.well-known/llms.txt +60 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +133 -23
- package/adapters/README.md +1 -1
- package/adapters/chatgpt/openapi.yaml +168 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +85 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +215 -19
- package/bin/postinstall.js +8 -2
- package/config/budget.json +18 -0
- package/config/gates/code-edit.json +61 -0
- package/config/gates/db-write.json +61 -0
- package/config/gates/default.json +154 -3
- package/config/gates/deploy.json +61 -0
- package/config/github-about.json +2 -1
- package/config/merge-quality-checks.json +23 -0
- package/config/model-tiers.json +11 -0
- package/openapi/openapi.yaml +168 -0
- package/package.json +47 -13
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-codex-bridge/.mcp.json +1 -1
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +1 -3
- package/plugins/codex-profile/.codex-plugin/plugin.json +1 -1
- package/plugins/codex-profile/.mcp.json +1 -1
- package/plugins/codex-profile/INSTALL.md +27 -4
- package/plugins/codex-profile/README.md +33 -9
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor-marketplace/README.md +2 -2
- package/plugins/cursor-marketplace/commands/capture-feedback.md +2 -2
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +3 -3
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +3 -2
- package/plugins/opencode-profile/INSTALL.md +1 -1
- package/public/blog.html +73 -0
- package/public/compare/mem0.html +189 -0
- package/public/compare/speclock.html +180 -0
- package/public/compare.html +12 -4
- package/public/guide.html +5 -5
- package/public/guides/claude-code-prevent-repeated-mistakes.html +161 -0
- package/public/guides/codex-cli-guardrails.html +158 -0
- package/public/guides/cursor-prevent-repeated-mistakes.html +161 -0
- package/public/guides/pre-action-gates.html +162 -0
- package/public/guides/stop-repeated-ai-agent-mistakes.html +159 -0
- package/public/index.html +169 -70
- package/public/learn/ai-agent-persistent-memory.html +1 -0
- package/public/lessons.html +334 -17
- package/public/llm-context.md +140 -0
- package/public/pro.html +24 -22
- package/scripts/__pycache__/train_from_feedback.cpython-312.pyc +0 -0
- package/scripts/access-anomaly-detector.js +1 -1
- package/scripts/adk-consolidator.js +1 -5
- package/scripts/agent-security-hardening.js +4 -6
- package/scripts/agentic-data-pipeline.js +1 -3
- package/scripts/async-job-runner.js +1 -5
- package/scripts/audit-trail.js +7 -5
- package/scripts/background-agent-governance.js +2 -10
- package/scripts/billing.js +2 -16
- package/scripts/budget-enforcer.js +173 -0
- package/scripts/build-codex-plugin.js +152 -0
- package/scripts/capture-railway-diagnostics.sh +97 -0
- package/scripts/check-congruence.js +133 -15
- package/scripts/claude-feedback-sync.js +320 -0
- package/scripts/cli-telemetry.js +4 -1
- package/scripts/commercial-offer.js +5 -7
- package/scripts/content-engine/linkedin-content-generator.js +154 -0
- package/scripts/content-engine/output/linkedin-memento-validation.md +17 -0
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +175 -0
- package/scripts/content-engine/reddit-thread-finder.js +154 -0
- package/scripts/context-engine.js +21 -6
- package/scripts/contextfs.js +33 -44
- package/scripts/dashboard.js +104 -0
- package/scripts/decision-journal.js +341 -0
- package/scripts/delegation-runtime.js +1 -5
- package/scripts/distribution-surfaces.js +26 -0
- package/scripts/document-intake.js +927 -0
- package/scripts/ephemeral-agent-store.js +1 -8
- package/scripts/evolution-state.js +1 -5
- package/scripts/experiment-tracker.js +1 -5
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-hf-dataset.js +1 -5
- package/scripts/export-training.js +1 -5
- package/scripts/feedback-attribution.js +1 -16
- package/scripts/feedback-history-distiller.js +1 -16
- package/scripts/feedback-loop.js +17 -5
- package/scripts/feedback-root-consolidator.js +2 -21
- package/scripts/feedback-session.js +49 -0
- package/scripts/feedback-to-rules.js +188 -28
- package/scripts/filesystem-search.js +1 -9
- package/scripts/fs-utils.js +104 -0
- package/scripts/gates-engine.js +149 -4
- package/scripts/github-about.js +32 -8
- package/scripts/gtm-revenue-loop.js +1 -5
- package/scripts/harness-selector.js +148 -0
- package/scripts/hosted-job-launcher.js +1 -5
- package/scripts/hybrid-feedback-context.js +7 -33
- package/scripts/intervention-policy.js +753 -0
- package/scripts/lesson-db.js +3 -18
- package/scripts/lesson-inference.js +194 -16
- package/scripts/lesson-retrieval.js +60 -24
- package/scripts/llm-client.js +59 -0
- package/scripts/local-model-profile.js +18 -2
- package/scripts/managed-lesson-agent.js +183 -0
- package/scripts/marketing-experiment.js +8 -22
- package/scripts/meta-agent-loop.js +624 -0
- package/scripts/metered-billing.js +1 -1
- package/scripts/model-tier-router.js +10 -1
- package/scripts/money-watcher.js +1 -4
- package/scripts/obsidian-export.js +1 -5
- package/scripts/operational-integrity.js +369 -34
- package/scripts/org-dashboard.js +6 -1
- package/scripts/per-step-scoring.js +2 -4
- package/scripts/pr-manager.js +201 -19
- package/scripts/pro-features.js +3 -2
- package/scripts/prompt-dlp.js +3 -3
- package/scripts/prove-adapters.js +2 -5
- package/scripts/prove-attribution.js +1 -5
- package/scripts/prove-automation.js +3 -5
- package/scripts/prove-cloudflare-sandbox.js +1 -3
- package/scripts/prove-data-pipeline.js +1 -3
- package/scripts/prove-intelligence.js +1 -3
- package/scripts/prove-lancedb.js +1 -5
- package/scripts/prove-local-intelligence.js +1 -3
- package/scripts/prove-packaged-runtime.js +326 -0
- package/scripts/prove-predictive-insights.js +1 -3
- package/scripts/prove-runtime.js +13 -0
- package/scripts/prove-training-export.js +1 -3
- package/scripts/prove-workflow-contract.js +1 -5
- package/scripts/rate-limiter.js +6 -4
- package/scripts/reddit-dm-outreach.js +14 -4
- package/scripts/schedule-manager.js +3 -5
- package/scripts/security-scanner.js +448 -0
- package/scripts/self-distill-agent.js +579 -0
- package/scripts/semantic-dedup.js +115 -0
- package/scripts/skill-exporter.js +1 -3
- package/scripts/skill-generator.js +1 -5
- package/scripts/social-analytics/engagement-audit.js +1 -18
- package/scripts/social-analytics/pollers/linkedin.js +26 -16
- package/scripts/social-analytics/publishers/linkedin.js +1 -1
- package/scripts/social-analytics/publishers/zernio.js +51 -0
- package/scripts/social-pipeline.js +1 -3
- package/scripts/social-post-hourly.js +47 -4
- package/scripts/statusline-links.js +6 -5
- package/scripts/statusline-local-stats.js +2 -0
- package/scripts/statusline.sh +38 -7
- package/scripts/sync-branch-protection.js +340 -0
- package/scripts/tessl-export.js +1 -3
- package/scripts/thumbgate-search.js +32 -1
- package/scripts/tool-kpi-tracker.js +1 -1
- package/scripts/tool-registry.js +108 -4
- package/scripts/vector-store.js +1 -5
- package/scripts/weekly-auto-post.js +1 -1
- package/scripts/workflow-sentinel.js +205 -4
- package/skills/thumbgate/SKILL.md +2 -2
- package/src/api/server.js +273 -4
- package/scripts/social-analytics/db/social-analytics.db-shm +0 -0
- /package/scripts/social-analytics/db/{social-analytics.db-wal → analytics.sqlite} +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const net = require('net');
|
|
9
|
+
const { execFileSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const ROOT = path.join(__dirname, '..');
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 15000;
|
|
13
|
+
const DEFAULT_PUBLISH_INSTALL_RETRIES = 6;
|
|
14
|
+
const DEFAULT_PUBLISH_INSTALL_DELAY_MS = 5000;
|
|
15
|
+
const MAX_PUBLISH_INSTALL_DELAY_MS = 30000;
|
|
16
|
+
const STATUSLINE_INPUT = JSON.stringify({ context_window: { used_percentage: 12 } });
|
|
17
|
+
|
|
18
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
19
|
+
const parsed = {};
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const token = argv[index];
|
|
22
|
+
if (!token.startsWith('--')) continue;
|
|
23
|
+
const [rawKey, inlineValue] = token.slice(2).split('=');
|
|
24
|
+
const key = rawKey.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
|
|
25
|
+
const value = inlineValue !== undefined ? inlineValue : argv[index + 1];
|
|
26
|
+
parsed[key] = value;
|
|
27
|
+
if (inlineValue === undefined) index += 1;
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function pkgVersion() {
|
|
33
|
+
return require(path.join(ROOT, 'package.json')).version;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function packCurrentRepo(packDir) {
|
|
37
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
38
|
+
const output = execFileSync('npm', ['pack', '--json', '--pack-destination', packDir], {
|
|
39
|
+
cwd: ROOT,
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
42
|
+
});
|
|
43
|
+
const parsed = JSON.parse(output);
|
|
44
|
+
const fileName = parsed && parsed[0] && parsed[0].filename;
|
|
45
|
+
if (!fileName) {
|
|
46
|
+
throw new Error('npm pack did not return a tarball filename');
|
|
47
|
+
}
|
|
48
|
+
return path.join(packDir, fileName);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installPackage(prefixDir, packageSpec) {
|
|
52
|
+
fs.mkdirSync(prefixDir, { recursive: true });
|
|
53
|
+
execFileSync('npm', ['install', '--prefix', prefixDir, '--no-fund', '--no-audit', packageSpec], {
|
|
54
|
+
cwd: ROOT,
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
57
|
+
});
|
|
58
|
+
return path.join(prefixDir, 'node_modules', '.bin', 'thumbgate');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isRemotePackageSpec(packageSpec) {
|
|
62
|
+
if (!packageSpec) return false;
|
|
63
|
+
return !/^(?:\.{0,2}\/|\/|file:)/.test(packageSpec) && !packageSpec.endsWith('.tgz');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isTransientRegistryMiss(error) {
|
|
67
|
+
const text = [
|
|
68
|
+
error && error.message,
|
|
69
|
+
error && error.stdout,
|
|
70
|
+
error && error.stderr,
|
|
71
|
+
]
|
|
72
|
+
.filter(Boolean)
|
|
73
|
+
.join('\n');
|
|
74
|
+
return /ETARGET|No matching version found|npm error code E404|404 Not Found/i.test(text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function installPackageWithRetry(prefixDir, packageSpec, options = {}) {
|
|
78
|
+
const installImpl = options.installImpl || installPackage;
|
|
79
|
+
const sleepImpl = options.sleepImpl || sleep;
|
|
80
|
+
const remotePackage = options.remotePackage !== undefined ? options.remotePackage : isRemotePackageSpec(packageSpec);
|
|
81
|
+
const attempts = remotePackage ? Number(options.attempts || DEFAULT_PUBLISH_INSTALL_RETRIES) : 1;
|
|
82
|
+
let delayMs = Number(options.delayMs || DEFAULT_PUBLISH_INSTALL_DELAY_MS);
|
|
83
|
+
let lastError = null;
|
|
84
|
+
|
|
85
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
86
|
+
if (attempt > 1) {
|
|
87
|
+
fs.rmSync(prefixDir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return installImpl(prefixDir, packageSpec);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
lastError = error;
|
|
93
|
+
const retryable = remotePackage && isTransientRegistryMiss(error) && attempt < attempts;
|
|
94
|
+
if (!retryable) {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
process.stderr.write(
|
|
98
|
+
`Retrying published package install for ${packageSpec} after transient registry miss (${attempt}/${attempts - 1})\n`
|
|
99
|
+
);
|
|
100
|
+
await sleepImpl(delayMs);
|
|
101
|
+
delayMs = Math.min(Math.round(delayMs * 1.5), MAX_PUBLISH_INSTALL_DELAY_MS);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw lastError || new Error(`Failed to install package ${packageSpec}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function request(url, timeoutMs = 2000) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const req = http.get(url, (res) => {
|
|
111
|
+
let body = '';
|
|
112
|
+
res.setEncoding('utf8');
|
|
113
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
114
|
+
res.on('end', () => resolve({ statusCode: res.statusCode, body }));
|
|
115
|
+
});
|
|
116
|
+
req.on('error', reject);
|
|
117
|
+
req.setTimeout(timeoutMs, () => {
|
|
118
|
+
req.destroy(new Error(`Timed out requesting ${url}`));
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sleep(ms) {
|
|
124
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getAvailablePort() {
|
|
128
|
+
return new Promise((resolve, reject) => {
|
|
129
|
+
const server = net.createServer();
|
|
130
|
+
server.listen(0, '127.0.0.1', () => {
|
|
131
|
+
const address = server.address();
|
|
132
|
+
const port = address && address.port;
|
|
133
|
+
server.close((error) => {
|
|
134
|
+
if (error) {
|
|
135
|
+
reject(error);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
resolve(port);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
server.on('error', reject);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function waitForHealthy(origin, expectedVersion, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
146
|
+
const startedAt = Date.now();
|
|
147
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
148
|
+
try {
|
|
149
|
+
const response = await request(`${origin}/health`);
|
|
150
|
+
if (response.statusCode === 200) {
|
|
151
|
+
const body = JSON.parse(response.body);
|
|
152
|
+
if (body.version === expectedVersion) {
|
|
153
|
+
return body;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
// Keep polling until the detached API server comes online.
|
|
158
|
+
}
|
|
159
|
+
await sleep(250);
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`Timed out waiting for packaged runtime health at ${origin}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderStatusline(runtimeBin, projectDir, env) {
|
|
165
|
+
return execFileSync(runtimeBin, ['statusline-render'], {
|
|
166
|
+
cwd: projectDir,
|
|
167
|
+
env,
|
|
168
|
+
encoding: 'utf8',
|
|
169
|
+
input: STATUSLINE_INPUT,
|
|
170
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
171
|
+
timeout: 10000,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function runtimeStatePath(homeDir) {
|
|
176
|
+
return path.join(homeDir, '.thumbgate', 'runtime', 'statusline-api.json');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function stopDetachedRuntime(homeDir) {
|
|
180
|
+
try {
|
|
181
|
+
const state = JSON.parse(fs.readFileSync(runtimeStatePath(homeDir), 'utf8'));
|
|
182
|
+
const pid = Number(state && state.pid);
|
|
183
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
184
|
+
try {
|
|
185
|
+
process.kill(pid, 'SIGTERM');
|
|
186
|
+
} catch {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
190
|
+
await sleep(100);
|
|
191
|
+
try {
|
|
192
|
+
process.kill(pid, 0);
|
|
193
|
+
} catch {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
try {
|
|
198
|
+
process.kill(pid, 'SIGKILL');
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore cleanup races.
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// No runtime state means nothing to clean up.
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function runPackagedRuntimeSmoke(options = {}) {
|
|
208
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'thumbgate-packaged-runtime-'));
|
|
209
|
+
const homeDir = path.join(tempRoot, 'home');
|
|
210
|
+
const projectDir = path.join(tempRoot, 'project');
|
|
211
|
+
const packDir = path.join(tempRoot, 'pack');
|
|
212
|
+
const runtimeDir = path.join(tempRoot, 'runtime');
|
|
213
|
+
const expectedVersion = options.expectedVersion || pkgVersion();
|
|
214
|
+
const feedbackDir = path.join(projectDir, '.thumbgate');
|
|
215
|
+
fs.mkdirSync(homeDir, { recursive: true });
|
|
216
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
217
|
+
fs.mkdirSync(feedbackDir, { recursive: true });
|
|
218
|
+
fs.writeFileSync(
|
|
219
|
+
path.join(feedbackDir, 'feedback-log.jsonl'),
|
|
220
|
+
[
|
|
221
|
+
JSON.stringify({ signal: 'positive', timestamp: '2026-04-08T20:00:00.000Z', context: 'packaged runtime smoke pass' }),
|
|
222
|
+
JSON.stringify({ signal: 'negative', timestamp: '2026-04-08T20:01:00.000Z', context: 'packaged runtime smoke fail path' }),
|
|
223
|
+
].join('\n') + '\n'
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const packageSpec = options.packageSpec || packCurrentRepo(packDir);
|
|
228
|
+
const runtimeBin = await installPackageWithRetry(runtimeDir, packageSpec, {
|
|
229
|
+
attempts: options.installAttempts,
|
|
230
|
+
delayMs: options.installDelayMs,
|
|
231
|
+
});
|
|
232
|
+
if (!fs.existsSync(runtimeBin)) {
|
|
233
|
+
throw new Error(`Installed runtime binary is missing: ${runtimeBin}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const port = await getAvailablePort();
|
|
237
|
+
const origin = `http://127.0.0.1:${port}`;
|
|
238
|
+
const env = {
|
|
239
|
+
...process.env,
|
|
240
|
+
HOME: homeDir,
|
|
241
|
+
THUMBGATE_PROJECT_DIR: projectDir,
|
|
242
|
+
THUMBGATE_LOCAL_API_ORIGIN: origin,
|
|
243
|
+
THUMBGATE_API_KEY: 'tg_packaged_runtime_smoke',
|
|
244
|
+
NO_COLOR: '1',
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const initialStatusline = renderStatusline(runtimeBin, projectDir, env);
|
|
248
|
+
if (!initialStatusline.includes(`ThumbGate v${expectedVersion}`)) {
|
|
249
|
+
throw new Error(`Statusline version mismatch before boot: ${initialStatusline.trim()}`);
|
|
250
|
+
}
|
|
251
|
+
if (!/(Dashboard|Dashboard…)/.test(initialStatusline) || !/(Lessons|Lessons…)/.test(initialStatusline)) {
|
|
252
|
+
throw new Error(`Statusline missing dashboard affordances before boot: ${initialStatusline.trim()}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const health = await waitForHealthy(origin, expectedVersion, Number(options.timeoutMs || DEFAULT_TIMEOUT_MS));
|
|
256
|
+
const dashboard = await request(`${origin}/dashboard`);
|
|
257
|
+
const lessons = await request(`${origin}/lessons`);
|
|
258
|
+
if (dashboard.statusCode !== 200) {
|
|
259
|
+
throw new Error(`Packaged dashboard returned ${dashboard.statusCode}`);
|
|
260
|
+
}
|
|
261
|
+
if (lessons.statusCode !== 200) {
|
|
262
|
+
throw new Error(`Packaged lessons returned ${lessons.statusCode}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const readyStatusline = renderStatusline(runtimeBin, projectDir, env);
|
|
266
|
+
if (!/(Dashboard|Dashboard…)/.test(readyStatusline)) {
|
|
267
|
+
throw new Error(`Ready statusline missing dashboard label: ${readyStatusline.trim()}`);
|
|
268
|
+
}
|
|
269
|
+
if (!/(Lessons|Lessons…)/.test(readyStatusline)) {
|
|
270
|
+
throw new Error(`Ready statusline missing lessons label: ${readyStatusline.trim()}`);
|
|
271
|
+
}
|
|
272
|
+
if (readyStatusline.includes(`${origin}/dashboard`)) {
|
|
273
|
+
throw new Error(`Ready statusline leaked dashboard URL: ${readyStatusline.trim()}`);
|
|
274
|
+
}
|
|
275
|
+
if (readyStatusline.includes(`${origin}/lessons`)) {
|
|
276
|
+
throw new Error(`Ready statusline leaked lessons URL: ${readyStatusline.trim()}`);
|
|
277
|
+
}
|
|
278
|
+
// Thumbs-up/down icons stay inline while dashboard + lessons remain compact
|
|
279
|
+
// labels, even after the local API is up.
|
|
280
|
+
if (!readyStatusline.includes('👍')) {
|
|
281
|
+
throw new Error(`Ready statusline missing thumbs-up icon: ${readyStatusline.trim()}`);
|
|
282
|
+
}
|
|
283
|
+
if (!readyStatusline.includes('👎')) {
|
|
284
|
+
throw new Error(`Ready statusline missing thumbs-down icon: ${readyStatusline.trim()}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
packageSpec,
|
|
289
|
+
expectedVersion,
|
|
290
|
+
origin,
|
|
291
|
+
health,
|
|
292
|
+
};
|
|
293
|
+
} finally {
|
|
294
|
+
await stopDetachedRuntime(homeDir);
|
|
295
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function main() {
|
|
300
|
+
const args = parseArgs();
|
|
301
|
+
const result = await runPackagedRuntimeSmoke({
|
|
302
|
+
packageSpec: args.packageSpec,
|
|
303
|
+
expectedVersion: args.expectedVersion,
|
|
304
|
+
timeoutMs: args.timeoutMs,
|
|
305
|
+
installAttempts: args.installAttempts,
|
|
306
|
+
installDelayMs: args.installDelayMs,
|
|
307
|
+
});
|
|
308
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (require.main === module) {
|
|
312
|
+
main().catch((error) => {
|
|
313
|
+
process.stderr.write(`${error.message || String(error)}\n`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
module.exports = {
|
|
319
|
+
getAvailablePort,
|
|
320
|
+
installPackageWithRetry,
|
|
321
|
+
isRemotePackageSpec,
|
|
322
|
+
isTransientRegistryMiss,
|
|
323
|
+
packCurrentRepo,
|
|
324
|
+
runPackagedRuntimeSmoke,
|
|
325
|
+
waitForHealthy,
|
|
326
|
+
};
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
+
const { ensureDir } = require('./fs-utils');
|
|
7
8
|
|
|
8
9
|
const ROOT = path.join(__dirname, '..');
|
|
9
10
|
|
|
@@ -16,9 +17,6 @@ function resolveProofPaths() {
|
|
|
16
17
|
};
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
function ensureDir(dirPath) {
|
|
20
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
21
|
-
}
|
|
22
20
|
|
|
23
21
|
function buildStubTelemetry() {
|
|
24
22
|
return {
|
package/scripts/prove-runtime.js
CHANGED
|
@@ -5,6 +5,7 @@ const os = require('os');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const { buildManagedScheduleCommand } = require('./schedule-manager');
|
|
8
|
+
const { runPackagedRuntimeSmoke } = require('./prove-packaged-runtime');
|
|
8
9
|
|
|
9
10
|
const ROOT = path.join(__dirname, '..');
|
|
10
11
|
const RUNNER_PATH = require.resolve('./async-job-runner');
|
|
@@ -275,6 +276,18 @@ async function run() {
|
|
|
275
276
|
}
|
|
276
277
|
},
|
|
277
278
|
},
|
|
279
|
+
{
|
|
280
|
+
id: 'RUNTIME-07',
|
|
281
|
+
desc: 'packaged thumbgate runtime boots local API and serves dashboard affordances',
|
|
282
|
+
fn: async () => {
|
|
283
|
+
const result = await runPackagedRuntimeSmoke({
|
|
284
|
+
expectedVersion: require(path.join(ROOT, 'package.json')).version,
|
|
285
|
+
});
|
|
286
|
+
if (!result.health || result.health.version !== require(path.join(ROOT, 'package.json')).version) {
|
|
287
|
+
throw new Error('Packaged runtime health version did not match package.json');
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
278
291
|
];
|
|
279
292
|
|
|
280
293
|
console.log('Interruptible Runtime - Proof Gate\n');
|
|
@@ -13,15 +13,13 @@ const fs = require('fs');
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const path = require('path');
|
|
15
15
|
const { execSync } = require('child_process');
|
|
16
|
+
const { ensureDir } = require('./fs-utils');
|
|
16
17
|
|
|
17
18
|
const ROOT = path.join(__dirname, '..');
|
|
18
19
|
function getProofDir() {
|
|
19
20
|
return process.env.THUMBGATE_PROOF_DIR || path.join(ROOT, 'proof');
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function ensureDir(d) {
|
|
23
|
-
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
24
|
-
}
|
|
25
23
|
|
|
26
24
|
function runTests() {
|
|
27
25
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const fs = require('node:fs');
|
|
3
3
|
const path = require('node:path');
|
|
4
|
+
const { ensureDir } = require('./fs-utils');
|
|
4
5
|
const {
|
|
5
6
|
runWorkflowContractValidation,
|
|
6
7
|
} = require('./validate-workflow-contract');
|
|
@@ -8,11 +9,6 @@ const {
|
|
|
8
9
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
9
10
|
const DEFAULT_PROOF_DIR = path.join(PROJECT_ROOT, 'proof', 'workflow-contract');
|
|
10
11
|
|
|
11
|
-
function ensureDir(dirPath) {
|
|
12
|
-
if (!fs.existsSync(dirPath)) {
|
|
13
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
12
|
|
|
17
13
|
function toMarkdown(report) {
|
|
18
14
|
const lines = [
|
package/scripts/rate-limiter.js
CHANGED
|
@@ -5,14 +5,16 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const {
|
|
7
7
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
8
|
+
PRO_PRICE_LABEL,
|
|
9
|
+
TEAM_PRICE_LABEL,
|
|
8
10
|
} = require('./commercial-offer');
|
|
9
11
|
|
|
10
12
|
const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
|
|
11
13
|
|
|
12
14
|
const FREE_TIER_LIMITS = {
|
|
13
|
-
capture_feedback: { daily:
|
|
15
|
+
capture_feedback: { daily: 3, label: 'feedback captures' },
|
|
14
16
|
search_lessons: { daily: 5, label: 'lesson searches' },
|
|
15
|
-
search_thumbgate: { daily:
|
|
17
|
+
search_thumbgate: { daily: 5, label: 'ThumbGate searches' },
|
|
16
18
|
commerce_recall: { daily: 5, label: 'commerce recalls' },
|
|
17
19
|
export_dpo: { daily: 0, label: 'DPO exports (Pro only)' },
|
|
18
20
|
export_databricks: { daily: 0, label: 'Databricks exports (Pro only)' },
|
|
@@ -20,7 +22,7 @@ const FREE_TIER_LIMITS = {
|
|
|
20
22
|
|
|
21
23
|
const FREE_TIER_MAX_GATES = 5;
|
|
22
24
|
|
|
23
|
-
const UPGRADE_MESSAGE = `
|
|
25
|
+
const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — dashboard and DPO export: ${PRO_MONTHLY_PAYMENT_LINK}\n Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
|
|
24
26
|
|
|
25
27
|
function isProTier(authContext) {
|
|
26
28
|
if (authContext && authContext.tier === 'pro') return true;
|
|
@@ -83,7 +85,7 @@ function checkLimit(action, authContext) {
|
|
|
83
85
|
const current = usage.counts[action] || 0;
|
|
84
86
|
|
|
85
87
|
if (current >= dailyLimit) {
|
|
86
|
-
return { allowed: false, message: UPGRADE_MESSAGE
|
|
88
|
+
return { allowed: false, message: `Free tier limit reached. Upgrade to Pro for unlimited: https://thumbgate-production.up.railway.app/pro\n${UPGRADE_MESSAGE}`, used: current, limit: dailyLimit };
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
// Increment
|
|
@@ -151,13 +151,23 @@ async function main() {
|
|
|
151
151
|
const messages = [
|
|
152
152
|
{
|
|
153
153
|
to: 'game-of-kton',
|
|
154
|
-
subject: '
|
|
155
|
-
text: 'Hey
|
|
154
|
+
subject: 'Quick question about your agent workflow',
|
|
155
|
+
text: 'Hey — you left some really thoughtful comments on the AI coding agent thread. I\'m building ThumbGate (agent mistake prevention via PreToolUse hooks) and your feedback was the most useful I got.\n\nI\'m trying to figure out if this is worth building further. Would you be open to a quick 15-min call this week? Just want to understand how you handle agent mistakes in your workflow.\n\nI\'ll give you lifetime Pro access regardless — no strings attached.\n\nHere\'s the repo if you want to look first: https://github.com/IgorGanapolsky/ThumbGate'
|
|
156
156
|
},
|
|
157
157
|
{
|
|
158
158
|
to: 'Deep_Ad1959',
|
|
159
|
-
subject: '
|
|
160
|
-
text: 'Hey
|
|
159
|
+
subject: 'Your context-dependent blocking idea',
|
|
160
|
+
text: 'Hey — your point about context-dependent blocking was really insightful. That\'s exactly the problem I\'m trying to solve with ThumbGate (using Thompson Sampling for adaptive gates instead of hard binary blocks).\n\nWould you be open to a quick 15-min call this week? I\'m trying to figure out what developers would actually pay for in this space. Your perspective would be genuinely valuable.\n\nLifetime Pro access is yours either way. Repo: https://github.com/IgorGanapolsky/ThumbGate'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
to: 'leogodin217',
|
|
164
|
+
subject: 'Quick question about AI agent safety in your workflow',
|
|
165
|
+
text: 'Hey — you engaged with one of my posts about AI coding agent tooling and your take stood out. I\'m building ThumbGate (prevents AI agents from repeating mistakes via automated prevention rules).\n\nI\'m at the stage where I need honest feedback from people who actually use agents daily. Would you do a quick 15-min call this week? Just want to understand your pain points.\n\nLifetime Pro access is yours regardless. Repo: https://github.com/IgorGanapolsky/ThumbGate'
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
to: 'Enthu-Cutlet-1337',
|
|
169
|
+
subject: 'Quick question about your AI coding agent setup',
|
|
170
|
+
text: 'Hey — you commented on one of my posts about agent memory/safety tooling and your feedback was one of the few that was genuinely useful.\n\nI\'m building ThumbGate (automated mistake prevention for AI coding agents) and I\'m trying to figure out if this solves a real problem or if everyone just uses CLAUDE.md files. Would you be open to a quick 15-min call this week?\n\nLifetime Pro access is yours either way — no pitch, just questions. Repo: https://github.com/IgorGanapolsky/ThumbGate'
|
|
161
171
|
}
|
|
162
172
|
];
|
|
163
173
|
|
|
@@ -6,13 +6,11 @@ const path = require('path');
|
|
|
6
6
|
const os = require('os');
|
|
7
7
|
const { execSync } = require('child_process');
|
|
8
8
|
const { buildAgenticDataPipelineJobSpec } = require('./agentic-data-pipeline');
|
|
9
|
+
const { ensureDir } = require('./fs-utils');
|
|
9
10
|
|
|
10
11
|
const SCHEDULES_DIR = path.join(os.homedir(), '.thumbgate', 'schedules');
|
|
11
12
|
const PLIST_PREFIX = 'com.thumbgate.schedule';
|
|
12
13
|
|
|
13
|
-
function ensureDir() {
|
|
14
|
-
if (!fs.existsSync(SCHEDULES_DIR)) fs.mkdirSync(SCHEDULES_DIR, { recursive: true });
|
|
15
|
-
}
|
|
16
14
|
|
|
17
15
|
function escapePlistString(value) {
|
|
18
16
|
return String(value || '')
|
|
@@ -153,7 +151,7 @@ function buildAgenticDataPipelineSchedule(params = {}) {
|
|
|
153
151
|
}
|
|
154
152
|
|
|
155
153
|
function createSchedule(params) {
|
|
156
|
-
ensureDir();
|
|
154
|
+
ensureDir(SCHEDULES_DIR);
|
|
157
155
|
|
|
158
156
|
const id = params.id || params.name || `sched_${Date.now()}`;
|
|
159
157
|
const calendarInterval = parseCronSpec(params.schedule);
|
|
@@ -214,7 +212,7 @@ function createSchedule(params) {
|
|
|
214
212
|
}
|
|
215
213
|
|
|
216
214
|
function listSchedules() {
|
|
217
|
-
ensureDir();
|
|
215
|
+
ensureDir(SCHEDULES_DIR);
|
|
218
216
|
const files = fs.readdirSync(SCHEDULES_DIR).filter(f => f.endsWith('.json'));
|
|
219
217
|
return files.map(f => {
|
|
220
218
|
try {
|