thumbgate 1.15.0 → 1.16.1
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/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +59 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +210 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +157 -8
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +57 -49
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +6 -6
- package/public/compare.html +29 -23
- package/public/dashboard.html +82 -10
- package/public/guide.html +28 -28
- package/public/index.html +216 -98
- package/public/learn.html +50 -22
- package/public/lessons.html +1 -1
- package/public/numbers.html +17 -17
- package/public/pro.html +82 -18
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-schema.js +18 -2
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +92 -4
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +16 -4
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +232 -55
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +63 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/src/api/server.js +381 -120
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- package/scripts/workflow-sprint-intake.js +0 -475
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const { PRODUCTHUNT_URL } = require('./distribution-surfaces');
|
|
5
|
-
const { getOperationalBillingSummary } = require('./operational-summary');
|
|
6
|
-
const { summarizeCreatorPerformance } = require('./creator-campaigns');
|
|
7
|
-
const { getFeedbackPaths } = require('./feedback-loop');
|
|
8
|
-
const { buildPredictiveInsights } = require('./predictive-insights');
|
|
9
|
-
const { getTelemetryAnalytics } = require('./telemetry-analytics');
|
|
10
|
-
|
|
11
|
-
const NPM_PACKAGE = 'thumbgate';
|
|
12
|
-
const GITHUB_REPO = 'IgorGanapolsky/ThumbGate';
|
|
13
|
-
const PLAUSIBLE_URL = 'https://plausible.io/thumbgate-production.up.railway.app';
|
|
14
|
-
const LANDING_PAGE = 'https://thumbgate-production.up.railway.app';
|
|
15
|
-
|
|
16
|
-
function httpsGet(url) {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
https.get(url, { headers: { 'User-Agent': 'thumbgate-analytics' } }, (res) => {
|
|
19
|
-
let data = '';
|
|
20
|
-
res.on('data', (chunk) => { data += chunk; });
|
|
21
|
-
res.on('end', () => {
|
|
22
|
-
if (res.statusCode >= 400) {
|
|
23
|
-
reject(new Error(`HTTP ${res.statusCode} for ${url}: ${data}`));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
|
|
27
|
-
});
|
|
28
|
-
}).on('error', reject);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function sparkline(values) {
|
|
33
|
-
const chars = '▁▂▃▄▅▆▇█';
|
|
34
|
-
const max = Math.max(...values, 1);
|
|
35
|
-
return values.map((v) => chars[Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)]).join('');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function fetchNpmMonthly() {
|
|
39
|
-
return httpsGet(`https://api.npmjs.org/downloads/range/last-month/${NPM_PACKAGE}`);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function fetchNpmWeekly() {
|
|
43
|
-
return httpsGet(`https://api.npmjs.org/downloads/point/last-week/${NPM_PACKAGE}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function fetchGitHub() {
|
|
47
|
-
return httpsGet(`https://api.github.com/repos/${GITHUB_REPO}`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function fetchNpmVersions() {
|
|
51
|
-
return httpsGet(`https://registry.npmjs.org/${NPM_PACKAGE}`);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function loadTelemetrySnapshot() {
|
|
55
|
-
const { FEEDBACK_DIR } = getFeedbackPaths();
|
|
56
|
-
return getTelemetryAnalytics(FEEDBACK_DIR);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getCounterValue(counter = {}, key) {
|
|
60
|
-
return Number(counter && counter[key]) || 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function mergeDimensionCounters(metricCounters = {}) {
|
|
64
|
-
const metrics = Object.keys(metricCounters);
|
|
65
|
-
const rows = new Map();
|
|
66
|
-
|
|
67
|
-
for (const metric of metrics) {
|
|
68
|
-
const counter = metricCounters[metric] || {};
|
|
69
|
-
for (const [rawKey, rawValue] of Object.entries(counter)) {
|
|
70
|
-
const key = String(rawKey || '').trim() || 'unknown';
|
|
71
|
-
const row = rows.get(key) || { key };
|
|
72
|
-
row[metric] = Number(rawValue || 0);
|
|
73
|
-
rows.set(key, row);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return Array.from(rows.values());
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function buildPredictiveStagingModel(telemetry = {}, billingSummary = {}) {
|
|
81
|
-
return {
|
|
82
|
-
dims: {
|
|
83
|
-
sources: mergeDimensionCounters({
|
|
84
|
-
pageViews: telemetry.visitors && telemetry.visitors.bySource,
|
|
85
|
-
checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsBySource,
|
|
86
|
-
acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionBySource,
|
|
87
|
-
paidCustomers: billingSummary.attribution && billingSummary.attribution.paidBySource,
|
|
88
|
-
bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueBySourceCents,
|
|
89
|
-
}),
|
|
90
|
-
creators: mergeDimensionCounters({
|
|
91
|
-
pageViews: telemetry.visitors && telemetry.visitors.byCreator,
|
|
92
|
-
checkoutStarts: telemetry.ctas && telemetry.ctas.checkoutStartsByCreator,
|
|
93
|
-
acquisitionLeads: billingSummary.attribution && billingSummary.attribution.acquisitionByCreator,
|
|
94
|
-
paidCustomers: billingSummary.attribution && billingSummary.attribution.paidByCreator,
|
|
95
|
-
bookedRevenueCents: billingSummary.attribution && billingSummary.attribution.bookedRevenueByCreatorCents,
|
|
96
|
-
workflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.byCreator,
|
|
97
|
-
qualifiedWorkflowSprintLeads: billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.byCreator,
|
|
98
|
-
}),
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function resolveProductHuntCount(primaryCounter = {}, secondaryCounter = {}) {
|
|
104
|
-
const primary = getCounterValue(primaryCounter, 'producthunt');
|
|
105
|
-
if (primary > 0) return primary;
|
|
106
|
-
return getCounterValue(secondaryCounter, 'producthunt');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function collectAnalytics(fetchers = {}) {
|
|
110
|
-
const fetchMonthly = fetchers.fetchNpmMonthly || fetchNpmMonthly;
|
|
111
|
-
const fetchWeekly = fetchers.fetchNpmWeekly || fetchNpmWeekly;
|
|
112
|
-
const fetchRepo = fetchers.fetchGitHub || fetchGitHub;
|
|
113
|
-
const fetchVersions = fetchers.fetchNpmVersions || fetchNpmVersions;
|
|
114
|
-
const fetchTelemetry = fetchers.fetchTelemetry || loadTelemetrySnapshot;
|
|
115
|
-
const fetchBillingSummary = fetchers.fetchBillingSummary || (async () => {
|
|
116
|
-
const result = await getOperationalBillingSummary();
|
|
117
|
-
return result.summary;
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const [monthly, weekly, github, npmMeta, telemetry, billingSummary] = await Promise.all([
|
|
121
|
-
fetchMonthly(),
|
|
122
|
-
fetchWeekly(),
|
|
123
|
-
fetchRepo(),
|
|
124
|
-
fetchVersions().catch(() => null),
|
|
125
|
-
Promise.resolve().then(() => fetchTelemetry()).catch(() => null),
|
|
126
|
-
Promise.resolve().then(() => fetchBillingSummary()).catch(() => null),
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
return { monthly, weekly, github, npmMeta, telemetry, billingSummary };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Estimate organic downloads by filtering out publish-day inflation.
|
|
134
|
-
* npm registry mirrors, bot crawlers (socket.dev, snyk, bundlephobia),
|
|
135
|
-
* and npm-stat bots all re-download on publish events.
|
|
136
|
-
* Weekend days with no publishes give the organic baseline.
|
|
137
|
-
*/
|
|
138
|
-
function estimateOrganicDownloads(dailyDownloads, publishDates) {
|
|
139
|
-
const publishSet = new Set(publishDates);
|
|
140
|
-
|
|
141
|
-
let weekendNoPublishTotal = 0;
|
|
142
|
-
let weekendNoPublishCount = 0;
|
|
143
|
-
let publishDayTotal = 0;
|
|
144
|
-
let publishDayCount = 0;
|
|
145
|
-
let noPublishDayTotal = 0;
|
|
146
|
-
let noPublishDayCount = 0;
|
|
147
|
-
|
|
148
|
-
for (const day of dailyDownloads) {
|
|
149
|
-
const dt = new Date(day.day + 'T00:00:00Z');
|
|
150
|
-
const isWeekend = dt.getUTCDay() === 0 || dt.getUTCDay() === 6;
|
|
151
|
-
const isPublishDay = publishSet.has(day.day);
|
|
152
|
-
|
|
153
|
-
if (isPublishDay) {
|
|
154
|
-
publishDayTotal += day.downloads;
|
|
155
|
-
publishDayCount++;
|
|
156
|
-
} else {
|
|
157
|
-
noPublishDayTotal += day.downloads;
|
|
158
|
-
noPublishDayCount++;
|
|
159
|
-
if (isWeekend) {
|
|
160
|
-
weekendNoPublishTotal += day.downloads;
|
|
161
|
-
weekendNoPublishCount++;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const organicDailyBaseline = weekendNoPublishCount > 0
|
|
167
|
-
? Math.round(weekendNoPublishTotal / weekendNoPublishCount)
|
|
168
|
-
: (noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0);
|
|
169
|
-
|
|
170
|
-
const totalDownloads = dailyDownloads.reduce((s, d) => s + d.downloads, 0);
|
|
171
|
-
const estimatedOrganic30d = organicDailyBaseline * 30;
|
|
172
|
-
const estimatedInflated = totalDownloads - estimatedOrganic30d;
|
|
173
|
-
const organicRate = totalDownloads > 0 ? (estimatedOrganic30d / totalDownloads * 100) : 0;
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
organicDailyBaseline,
|
|
177
|
-
estimatedOrganic30d,
|
|
178
|
-
estimatedInflated: Math.max(0, estimatedInflated),
|
|
179
|
-
organicRate: Math.min(100, organicRate),
|
|
180
|
-
organicWeekly: organicDailyBaseline * 7,
|
|
181
|
-
publishDayAvg: publishDayCount > 0 ? Math.round(publishDayTotal / publishDayCount) : 0,
|
|
182
|
-
noPublishDayAvg: noPublishDayCount > 0 ? Math.round(noPublishDayTotal / noPublishDayCount) : 0,
|
|
183
|
-
publishDayCount,
|
|
184
|
-
totalDownloads,
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function formatCreatorRows(telemetry = null, billingSummary = null) {
|
|
189
|
-
return summarizeCreatorPerformance(telemetry, billingSummary).map((entry, index) => {
|
|
190
|
-
const revenueDollars = (entry.bookedRevenueCents / 100).toFixed(2);
|
|
191
|
-
return ` ${index + 1}. ${entry.creator} — rev $${revenueDollars}, paid ${entry.paidOrders}, sprint ${entry.qualifiedSprintLeads}/${entry.sprintLeads}, checkouts ${entry.checkoutStarts}, visitors ${entry.visitors}`;
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function formatReport(monthly, weekly, github, npmMeta, telemetry = null, billingSummary = null) {
|
|
196
|
-
const weeklyDownloads = weekly.downloads || 0;
|
|
197
|
-
const allDays = monthly.downloads || [];
|
|
198
|
-
const monthlyDownloads = allDays.reduce((sum, d) => sum + d.downloads, 0);
|
|
199
|
-
const dailyValues = allDays.slice(-7).map((d) => d.downloads);
|
|
200
|
-
const trend = sparkline(dailyValues);
|
|
201
|
-
|
|
202
|
-
// Extract publish dates from npm registry metadata
|
|
203
|
-
const publishDates = [];
|
|
204
|
-
if (npmMeta && npmMeta.time) {
|
|
205
|
-
for (const [version, timestamp] of Object.entries(npmMeta.time)) {
|
|
206
|
-
if (version !== 'created' && version !== 'modified') {
|
|
207
|
-
publishDates.push(timestamp.slice(0, 10));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const organic = estimateOrganicDownloads(allDays, publishDates);
|
|
213
|
-
const productHuntVisitors = telemetry
|
|
214
|
-
? resolveProductHuntCount(telemetry.visitors.byTrafficChannel, telemetry.visitors.bySource)
|
|
215
|
-
: 0;
|
|
216
|
-
const productHuntCtas = telemetry
|
|
217
|
-
? resolveProductHuntCount(telemetry.ctas.byTrafficChannel, telemetry.ctas.bySource)
|
|
218
|
-
: 0;
|
|
219
|
-
const productHuntCheckouts = telemetry
|
|
220
|
-
? resolveProductHuntCount(telemetry.ctas.checkoutStartsByTrafficChannel, telemetry.ctas.checkoutStartsBySource)
|
|
221
|
-
: 0;
|
|
222
|
-
const telemetryWindow = telemetry && telemetry.window ? telemetry.window : 'all';
|
|
223
|
-
const telemetryLastSeen = telemetry && telemetry.latestSeenAt ? telemetry.latestSeenAt : 'none';
|
|
224
|
-
const creatorRows = formatCreatorRows(telemetry, billingSummary);
|
|
225
|
-
const predictive = buildPredictiveInsights({
|
|
226
|
-
telemetryAnalytics: telemetry || {},
|
|
227
|
-
billingSummary: billingSummary || {},
|
|
228
|
-
stagingModel: buildPredictiveStagingModel(telemetry || {}, billingSummary || {}),
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const lines = [
|
|
232
|
-
'',
|
|
233
|
-
'╔══════════════════════════════════════════════════════════════════╗',
|
|
234
|
-
'║ ThumbGate — Unified Analytics Snapshot ║',
|
|
235
|
-
'╚══════════════════════════════════════════════════════════════════╝',
|
|
236
|
-
'',
|
|
237
|
-
'📦 npm — thumbgate (REPORTED)',
|
|
238
|
-
` Weekly downloads: ${weeklyDownloads.toLocaleString()}`,
|
|
239
|
-
` Monthly downloads: ${monthlyDownloads.toLocaleString()}`,
|
|
240
|
-
` Daily trend (7d): ${trend} [${dailyValues.join(', ')}]`,
|
|
241
|
-
'',
|
|
242
|
-
'🔬 npm — ORGANIC ESTIMATE (excluding publish-day + bot inflation)',
|
|
243
|
-
` Organic baseline: ~${organic.organicDailyBaseline}/day → ~${organic.organicWeekly}/week → ~${organic.estimatedOrganic30d.toLocaleString()}/month`,
|
|
244
|
-
` Publish-day avg: ${organic.publishDayAvg}/day (${organic.publishDayCount} publish days this period)`,
|
|
245
|
-
` No-publish avg: ${organic.noPublishDayAvg}/day`,
|
|
246
|
-
` Organic rate: ${organic.organicRate.toFixed(1)}% of reported downloads`,
|
|
247
|
-
` Inflated by: ~${organic.estimatedInflated.toLocaleString()} downloads (registry mirrors, bots, self-installs)`,
|
|
248
|
-
'',
|
|
249
|
-
'⭐ GitHub — IgorGanapolsky/ThumbGate',
|
|
250
|
-
` Stars: ${(github.stargazers_count || 0).toLocaleString()}`,
|
|
251
|
-
` Forks: ${(github.forks_count || 0).toLocaleString()}`,
|
|
252
|
-
` Open issues: ${(github.open_issues_count || 0).toLocaleString()}`,
|
|
253
|
-
` Watchers: ${(github.subscribers_count || 0).toLocaleString()}`,
|
|
254
|
-
'',
|
|
255
|
-
'🌐 Landing Page',
|
|
256
|
-
` Plausible: ${PLAUSIBLE_URL}`,
|
|
257
|
-
` ⚠️ Check Plausible to exclude your own IP (Settings → filter)`,
|
|
258
|
-
'',
|
|
259
|
-
'🚀 ProductHunt',
|
|
260
|
-
` Listing: ${PRODUCTHUNT_URL}`,
|
|
261
|
-
` Tracked: utm_source=producthunt → traffic_channel=producthunt`,
|
|
262
|
-
` Visitors: ${productHuntVisitors}`,
|
|
263
|
-
` CTA clicks: ${productHuntCtas}`,
|
|
264
|
-
` Checkouts: ${productHuntCheckouts}`,
|
|
265
|
-
` Window: ${telemetryWindow}`,
|
|
266
|
-
` Last seen: ${telemetryLastSeen}`,
|
|
267
|
-
'',
|
|
268
|
-
'🎥 Creator Partnerships',
|
|
269
|
-
' Ranked: booked revenue → paid orders → qualified sprint leads → checkouts',
|
|
270
|
-
...(creatorRows.length > 0 ? creatorRows : [' No attributed creator campaigns yet.']),
|
|
271
|
-
'',
|
|
272
|
-
'🔮 Predictive Insights',
|
|
273
|
-
` Pro propensity: ${predictive.upgradePropensity.pro.band} (${predictive.upgradePropensity.pro.score})`,
|
|
274
|
-
` Team propensity: ${predictive.upgradePropensity.team.band} (${predictive.upgradePropensity.team.score})`,
|
|
275
|
-
` Revenue forecast: $${(predictive.revenueForecast.predictedBookedRevenueCents / 100).toFixed(2)} (+$${(predictive.revenueForecast.incrementalOpportunityCents / 100).toFixed(2)} opportunity)`,
|
|
276
|
-
` Predictive alerts:${predictive.anomalySummary.count} (${predictive.anomalySummary.severity})`,
|
|
277
|
-
...(predictive.topCreators[0]
|
|
278
|
-
? [` Top creator opp: ${predictive.topCreators[0].key} → +$${(predictive.topCreators[0].opportunityRevenueCents / 100).toFixed(2)}`]
|
|
279
|
-
: [' Top creator opp: none yet']),
|
|
280
|
-
...(predictive.topSources[0]
|
|
281
|
-
? [` Top channel opp: ${predictive.topSources[0].key} → +$${(predictive.topSources[0].opportunityRevenueCents / 100).toFixed(2)}`]
|
|
282
|
-
: [' Top channel opp: none yet']),
|
|
283
|
-
'',
|
|
284
|
-
'🔗 UTM links for sharing (tracks referral source in Plausible)',
|
|
285
|
-
` Twitter: ${LANDING_PAGE}?utm_source=twitter&utm_medium=social&utm_campaign=launch`,
|
|
286
|
-
` LinkedIn: ${LANDING_PAGE}?utm_source=linkedin&utm_medium=social&utm_campaign=launch`,
|
|
287
|
-
` Reddit: ${LANDING_PAGE}?utm_source=reddit&utm_medium=social&utm_campaign=launch`,
|
|
288
|
-
` HackerNews: ${LANDING_PAGE}?utm_source=hackernews&utm_medium=social&utm_campaign=launch`,
|
|
289
|
-
'',
|
|
290
|
-
'📏 HONEST METRICS (use these, not the inflated ones)',
|
|
291
|
-
` Real npm traction: ~${organic.organicWeekly} downloads/week`,
|
|
292
|
-
` GitHub stars: ${(github.stargazers_count || 0)}`,
|
|
293
|
-
` ProductHunt: check listing for upvotes/followers`,
|
|
294
|
-
` Landing page: check Plausible (exclude your IP!)`,
|
|
295
|
-
'',
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
return lines.join('\n');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function run(options = {}) {
|
|
302
|
-
const log = options.log || console.log;
|
|
303
|
-
const error = options.error || console.error;
|
|
304
|
-
const exit = options.exit || process.exit;
|
|
305
|
-
|
|
306
|
-
try {
|
|
307
|
-
const { monthly, weekly, github, npmMeta, telemetry, billingSummary } = await collectAnalytics(options.fetchers);
|
|
308
|
-
log(formatReport(monthly, weekly, github, npmMeta, telemetry, billingSummary));
|
|
309
|
-
} catch (err) {
|
|
310
|
-
error('Analytics fetch failed:', err.message);
|
|
311
|
-
exit(1);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
module.exports = {
|
|
316
|
-
run,
|
|
317
|
-
collectAnalytics,
|
|
318
|
-
formatReport,
|
|
319
|
-
fetchNpmMonthly,
|
|
320
|
-
fetchNpmWeekly,
|
|
321
|
-
fetchGitHub,
|
|
322
|
-
fetchNpmVersions,
|
|
323
|
-
estimateOrganicDownloads,
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
if (require.main === module) {
|
|
327
|
-
run();
|
|
328
|
-
}
|
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
|
|
7
|
-
const { ensureDir } = require('./fs-utils');
|
|
8
|
-
const {
|
|
9
|
-
executeJob,
|
|
10
|
-
readJobState,
|
|
11
|
-
resumeJob,
|
|
12
|
-
} = require('./async-job-runner');
|
|
13
|
-
const {
|
|
14
|
-
createCheckpoint,
|
|
15
|
-
advanceCheckpoint,
|
|
16
|
-
loadCheckpoint,
|
|
17
|
-
saveCheckpoint,
|
|
18
|
-
} = require('./workflow-gate-checkpoint');
|
|
19
|
-
const { appendWorkflowRun } = require('./workflow-runs');
|
|
20
|
-
|
|
21
|
-
function normalizeText(value) {
|
|
22
|
-
if (value === undefined || value === null) return '';
|
|
23
|
-
return String(value).trim();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function slugify(value, fallback = 'workflow') {
|
|
27
|
-
// Avoid any `-+` quantifier in an edge-anchored regex (Sonar javascript:S5852
|
|
28
|
-
// still flags even the anchored form). Strip edge dashes with a linear scan.
|
|
29
|
-
const collapsed = normalizeText(value).toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
30
|
-
let start = 0;
|
|
31
|
-
let end = collapsed.length;
|
|
32
|
-
while (start < end && collapsed.charCodeAt(start) === 45) start += 1;
|
|
33
|
-
while (end > start && collapsed.charCodeAt(end - 1) === 45) end -= 1;
|
|
34
|
-
const normalized = collapsed.slice(start, end);
|
|
35
|
-
return normalized || fallback;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function getWorkflowPaths(workflowId, cwd = process.cwd()) {
|
|
39
|
-
const rootDir = path.join(cwd, '.thumbgate', 'autonomous-workflows', workflowId);
|
|
40
|
-
return {
|
|
41
|
-
rootDir,
|
|
42
|
-
checkpointPath: path.join(rootDir, 'checkpoint.json'),
|
|
43
|
-
reportJsonPath: path.join(rootDir, 'report.json'),
|
|
44
|
-
reportMdPath: path.join(rootDir, 'report.md'),
|
|
45
|
-
planPath: path.join(rootDir, 'plan.json'),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function normalizePlan(input, workflowId) {
|
|
50
|
-
if (Array.isArray(input)) {
|
|
51
|
-
return {
|
|
52
|
-
workflowId,
|
|
53
|
-
summary: input.map((step) => normalizeText(step)).filter(Boolean).join(' | ') || 'Execution plan ready',
|
|
54
|
-
steps: input
|
|
55
|
-
.map((step, index) => ({
|
|
56
|
-
id: `step_${index + 1}`,
|
|
57
|
-
description: normalizeText(step),
|
|
58
|
-
}))
|
|
59
|
-
.filter((step) => step.description),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (input && typeof input === 'object') {
|
|
64
|
-
const steps = Array.isArray(input.steps)
|
|
65
|
-
? input.steps
|
|
66
|
-
.map((step, index) => {
|
|
67
|
-
if (typeof step === 'string') {
|
|
68
|
-
return {
|
|
69
|
-
id: `step_${index + 1}`,
|
|
70
|
-
description: normalizeText(step),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (step && typeof step === 'object') {
|
|
75
|
-
return {
|
|
76
|
-
id: normalizeText(step.id) || `step_${index + 1}`,
|
|
77
|
-
description: normalizeText(step.description || step.summary || step.name),
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return null;
|
|
82
|
-
})
|
|
83
|
-
.filter(Boolean)
|
|
84
|
-
: [];
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
workflowId,
|
|
88
|
-
summary: normalizeText(input.summary) || steps.map((step) => step.description).join(' | ') || 'Execution plan ready',
|
|
89
|
-
steps,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const summary = normalizeText(input) || 'Execution plan ready';
|
|
94
|
-
return {
|
|
95
|
-
workflowId,
|
|
96
|
-
summary,
|
|
97
|
-
steps: summary ? [{ id: 'step_1', description: summary }] : [],
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function buildDefaultPlan(spec, workflowId) {
|
|
102
|
-
const executionSteps = Array.isArray(spec.stages)
|
|
103
|
-
? spec.stages.map((stage, index) => normalizeText(stage && (stage.name || stage.context || stage.command)) || `Stage ${index + 1}`)
|
|
104
|
-
: [];
|
|
105
|
-
|
|
106
|
-
return normalizePlan({
|
|
107
|
-
summary: normalizeText(spec.planSummary) || `Run ${executionSteps.length || 0} execution stage(s) and verify output`,
|
|
108
|
-
steps: [
|
|
109
|
-
{ id: 'intent', description: normalizeText(spec.intent) || 'Intent captured' },
|
|
110
|
-
{ id: 'plan', description: 'Execution plan generated' },
|
|
111
|
-
...executionSteps.map((description, index) => ({
|
|
112
|
-
id: `execute_${index + 1}`,
|
|
113
|
-
description,
|
|
114
|
-
})),
|
|
115
|
-
{ id: 'verify', description: 'Verification loop completed' },
|
|
116
|
-
{ id: 'report', description: 'Evidence-backed report recorded' },
|
|
117
|
-
],
|
|
118
|
-
workflowId,
|
|
119
|
-
}, workflowId);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function resolvePlan(spec, workflowId) {
|
|
123
|
-
if (typeof spec.plan === 'function') {
|
|
124
|
-
return normalizePlan(spec.plan(spec), workflowId);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (spec.plan) {
|
|
128
|
-
return normalizePlan(spec.plan, workflowId);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return buildDefaultPlan(spec, workflowId);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function buildExecutionJob(spec, workflowId, paths, plan) {
|
|
135
|
-
return {
|
|
136
|
-
id: spec.jobId || `${workflowId}-execution`,
|
|
137
|
-
tags: Array.isArray(spec.tags) ? spec.tags : [],
|
|
138
|
-
skill: spec.skill || 'autonomous-workflow',
|
|
139
|
-
partnerProfile: spec.partnerProfile || null,
|
|
140
|
-
verificationMode: spec.verificationMode === 'none' ? 'none' : 'standard',
|
|
141
|
-
autoImprove: spec.autoImprove !== false,
|
|
142
|
-
recordFeedback: spec.recordFeedback !== false,
|
|
143
|
-
stages: Array.isArray(spec.stages) ? spec.stages : [],
|
|
144
|
-
metadata: {
|
|
145
|
-
workflowId,
|
|
146
|
-
planSummary: plan.summary,
|
|
147
|
-
workflowRoot: paths.rootDir,
|
|
148
|
-
},
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function writeWorkflowPlan(paths, plan) {
|
|
153
|
-
ensureDir(paths.rootDir);
|
|
154
|
-
fs.writeFileSync(paths.planPath, `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
|
|
155
|
-
return paths.planPath;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function collectEvidenceArtifacts(paths, executionResult, extraArtifacts = []) {
|
|
159
|
-
return [
|
|
160
|
-
paths.checkpointPath,
|
|
161
|
-
paths.planPath,
|
|
162
|
-
paths.reportJsonPath,
|
|
163
|
-
paths.reportMdPath,
|
|
164
|
-
executionResult && executionResult.jobStatePath ? executionResult.jobStatePath : null,
|
|
165
|
-
...extraArtifacts,
|
|
166
|
-
].filter(Boolean);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function writeWorkflowReport(paths, report) {
|
|
170
|
-
ensureDir(paths.rootDir);
|
|
171
|
-
fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
172
|
-
|
|
173
|
-
const markdown = [
|
|
174
|
-
`# ${report.workflowName}`,
|
|
175
|
-
'',
|
|
176
|
-
`- Workflow ID: ${report.workflowId}`,
|
|
177
|
-
`- Status: ${report.status}`,
|
|
178
|
-
`- Intent: ${report.intent}`,
|
|
179
|
-
`- Verification accepted: ${report.verification ? String(report.verification.accepted) : 'skipped'}`,
|
|
180
|
-
`- Evidence artifacts: ${report.evidenceArtifacts.length}`,
|
|
181
|
-
'',
|
|
182
|
-
'## Plan',
|
|
183
|
-
'',
|
|
184
|
-
report.plan.summary,
|
|
185
|
-
'',
|
|
186
|
-
...report.plan.steps.map((step) => `- ${step.id}: ${step.description}`),
|
|
187
|
-
'',
|
|
188
|
-
'## Execution',
|
|
189
|
-
'',
|
|
190
|
-
...report.execution.stageHistory.map((stage) => `- ${stage.name} @ ${stage.completedAt}`),
|
|
191
|
-
'',
|
|
192
|
-
'## Evidence Artifacts',
|
|
193
|
-
'',
|
|
194
|
-
...report.evidenceArtifacts.map((artifact) => `- ${artifact}`),
|
|
195
|
-
].join('\n');
|
|
196
|
-
|
|
197
|
-
fs.writeFileSync(paths.reportMdPath, `${markdown}\n`, 'utf8');
|
|
198
|
-
return {
|
|
199
|
-
json: paths.reportJsonPath,
|
|
200
|
-
markdown: paths.reportMdPath,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, feedbackDir) {
|
|
205
|
-
const proofBacked = report.status === 'completed'
|
|
206
|
-
&& (!report.verification || report.verification.accepted)
|
|
207
|
-
&& evidenceArtifacts.length > 0;
|
|
208
|
-
|
|
209
|
-
return appendWorkflowRun({
|
|
210
|
-
workflowId: report.workflowId,
|
|
211
|
-
workflowName: report.workflowName,
|
|
212
|
-
owner: spec.owner || 'automation',
|
|
213
|
-
runtime: 'node',
|
|
214
|
-
status: report.status,
|
|
215
|
-
customerType: spec.customerType || 'internal_dogfood',
|
|
216
|
-
teamId: spec.teamId || null,
|
|
217
|
-
reviewed: proofBacked,
|
|
218
|
-
reviewedBy: proofBacked ? (spec.reviewedBy || 'automation') : null,
|
|
219
|
-
proofBacked,
|
|
220
|
-
proofArtifacts: evidenceArtifacts,
|
|
221
|
-
source: spec.source || 'autonomous-workflow',
|
|
222
|
-
metadata: {
|
|
223
|
-
intent: report.intent,
|
|
224
|
-
planSummary: report.plan.summary,
|
|
225
|
-
verificationAttempts: report.verification ? report.verification.attempts : 0,
|
|
226
|
-
executionJobId: report.execution.jobId,
|
|
227
|
-
},
|
|
228
|
-
}, feedbackDir);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function runAutonomousWorkflow(spec = {}, options = {}) {
|
|
232
|
-
const cwd = options.cwd || process.cwd();
|
|
233
|
-
const workflowId = normalizeText(spec.workflowId) || slugify(spec.name || spec.intent, 'autonomous-workflow');
|
|
234
|
-
const workflowName = normalizeText(spec.name) || `Autonomous workflow ${workflowId}`;
|
|
235
|
-
const intent = normalizeText(spec.intent) || 'Intent not provided';
|
|
236
|
-
const paths = getWorkflowPaths(workflowId, cwd);
|
|
237
|
-
const plan = resolvePlan(spec, workflowId);
|
|
238
|
-
|
|
239
|
-
writeWorkflowPlan(paths, plan);
|
|
240
|
-
|
|
241
|
-
let checkpoint = createCheckpoint({
|
|
242
|
-
workflowId,
|
|
243
|
-
phase: 'intent',
|
|
244
|
-
status: 'running',
|
|
245
|
-
intent: { summary: intent },
|
|
246
|
-
plan,
|
|
247
|
-
evidence: [paths.planPath],
|
|
248
|
-
metadata: {
|
|
249
|
-
workflowName,
|
|
250
|
-
},
|
|
251
|
-
});
|
|
252
|
-
saveCheckpoint(checkpoint, paths.checkpointPath);
|
|
253
|
-
|
|
254
|
-
checkpoint = advanceCheckpoint(checkpoint, {
|
|
255
|
-
phase: 'plan',
|
|
256
|
-
status: 'running',
|
|
257
|
-
plan,
|
|
258
|
-
evidence: [paths.planPath],
|
|
259
|
-
});
|
|
260
|
-
saveCheckpoint(checkpoint, paths.checkpointPath);
|
|
261
|
-
|
|
262
|
-
const job = buildExecutionJob(spec, workflowId, paths, plan);
|
|
263
|
-
const executionResult = options.resume === true
|
|
264
|
-
? resumeJob(job.id, job)
|
|
265
|
-
: executeJob(job);
|
|
266
|
-
const jobState = readJobState(job.id);
|
|
267
|
-
|
|
268
|
-
checkpoint = advanceCheckpoint(checkpoint, {
|
|
269
|
-
phase: 'verify',
|
|
270
|
-
status: executionResult.status,
|
|
271
|
-
evidence: jobState && jobState.verification ? [paths.checkpointPath] : [],
|
|
272
|
-
metadata: {
|
|
273
|
-
executionJobId: job.id,
|
|
274
|
-
executionStatus: executionResult.status,
|
|
275
|
-
},
|
|
276
|
-
});
|
|
277
|
-
saveCheckpoint(checkpoint, paths.checkpointPath);
|
|
278
|
-
|
|
279
|
-
const report = {
|
|
280
|
-
workflowId,
|
|
281
|
-
workflowName,
|
|
282
|
-
status: executionResult.status,
|
|
283
|
-
intent,
|
|
284
|
-
plan,
|
|
285
|
-
execution: {
|
|
286
|
-
jobId: job.id,
|
|
287
|
-
status: executionResult.status,
|
|
288
|
-
stageHistory: Array.isArray(jobState && jobState.stageHistory) ? jobState.stageHistory : [],
|
|
289
|
-
checkpointCount: Array.isArray(jobState && jobState.checkpoints) ? jobState.checkpoints.length : 0,
|
|
290
|
-
currentContext: jobState && jobState.currentContext ? jobState.currentContext : '',
|
|
291
|
-
jobStatePath: jobState ? path.join(getFeedbackDir(options.feedbackDir), 'jobs', job.id, 'state.json') : null,
|
|
292
|
-
},
|
|
293
|
-
verification: executionResult.phases ? executionResult.phases.verification : null,
|
|
294
|
-
phases: executionResult.phases || null,
|
|
295
|
-
timestamp: new Date().toISOString(),
|
|
296
|
-
evidenceArtifacts: [],
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const evidenceArtifacts = collectEvidenceArtifacts(paths, report.execution, spec.proofArtifacts);
|
|
300
|
-
report.evidenceArtifacts = evidenceArtifacts;
|
|
301
|
-
|
|
302
|
-
checkpoint = advanceCheckpoint(checkpoint, {
|
|
303
|
-
phase: 'report',
|
|
304
|
-
status: executionResult.status,
|
|
305
|
-
report: {
|
|
306
|
-
status: report.status,
|
|
307
|
-
generatedAt: report.timestamp,
|
|
308
|
-
},
|
|
309
|
-
evidence: evidenceArtifacts,
|
|
310
|
-
});
|
|
311
|
-
saveCheckpoint(checkpoint, paths.checkpointPath);
|
|
312
|
-
|
|
313
|
-
writeWorkflowReport(paths, report);
|
|
314
|
-
report.workflowRun = recordAutonomousWorkflowRun(spec, report, evidenceArtifacts, options.feedbackDir);
|
|
315
|
-
fs.writeFileSync(paths.reportJsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
|
|
316
|
-
|
|
317
|
-
return report;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function getFeedbackDir(feedbackDir) {
|
|
321
|
-
if (feedbackDir) return feedbackDir;
|
|
322
|
-
return process.env.THUMBGATE_FEEDBACK_DIR || path.join(process.cwd(), '.thumbgate');
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function resumeAutonomousWorkflow(spec = {}, options = {}) {
|
|
326
|
-
return runAutonomousWorkflow(spec, { ...options, resume: true });
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function readWorkflowReport(workflowId, options = {}) {
|
|
330
|
-
const paths = getWorkflowPaths(workflowId, options.cwd || process.cwd());
|
|
331
|
-
if (!fs.existsSync(paths.reportJsonPath)) return null;
|
|
332
|
-
return JSON.parse(fs.readFileSync(paths.reportJsonPath, 'utf8'));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function isCliInvocation(argv = process.argv) {
|
|
336
|
-
const invokedPath = argv[1];
|
|
337
|
-
return invokedPath ? path.resolve(invokedPath) === __filename : false;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function parseArgs(argv = process.argv.slice(2)) {
|
|
341
|
-
const args = {};
|
|
342
|
-
for (const arg of argv) {
|
|
343
|
-
if (!arg.startsWith('--')) continue;
|
|
344
|
-
const [key, ...rest] = arg.slice(2).split('=');
|
|
345
|
-
args[key] = rest.length > 0 ? rest.join('=') : true;
|
|
346
|
-
}
|
|
347
|
-
return args;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (isCliInvocation()) {
|
|
351
|
-
const args = parseArgs();
|
|
352
|
-
if (!args.file) {
|
|
353
|
-
console.error('Usage: node scripts/autonomous-workflow.js --file=workflow.json [--resume]');
|
|
354
|
-
process.exit(1);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
const specPath = path.resolve(args.file);
|
|
358
|
-
const spec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
|
|
359
|
-
const report = args.resume ? resumeAutonomousWorkflow(spec) : runAutonomousWorkflow(spec);
|
|
360
|
-
console.log(JSON.stringify(report, null, 2));
|
|
361
|
-
process.exit(report.status === 'completed' ? 0 : 1);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
module.exports = {
|
|
365
|
-
buildDefaultPlan,
|
|
366
|
-
collectEvidenceArtifacts,
|
|
367
|
-
getWorkflowPaths,
|
|
368
|
-
normalizePlan,
|
|
369
|
-
parseArgs,
|
|
370
|
-
readWorkflowReport,
|
|
371
|
-
recordAutonomousWorkflowRun,
|
|
372
|
-
resumeAutonomousWorkflow,
|
|
373
|
-
runAutonomousWorkflow,
|
|
374
|
-
slugify,
|
|
375
|
-
writeWorkflowPlan,
|
|
376
|
-
writeWorkflowReport,
|
|
377
|
-
};
|