thumbgate 1.14.1 → 1.16.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/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 +60 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +217 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +211 -8
- package/config/enforcement.json +59 -7
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/default.json +33 -0
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +2 -1
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +70 -51
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +13 -7
- package/public/compare.html +29 -23
- package/public/dashboard.html +105 -12
- package/public/guide.html +28 -28
- package/public/index.html +233 -97
- package/public/learn.html +87 -20
- package/public/lessons.html +26 -2
- package/public/numbers.html +271 -0
- package/public/pro.html +89 -19
- 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-feedback.js +2 -1
- package/scripts/cli-schema.js +60 -5
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/commercial-offer.js +1 -1
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/contextfs.js +214 -32
- 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 +141 -9
- 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 +145 -1
- 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-canonical.js +181 -0
- package/scripts/lesson-db.js +71 -10
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/lesson-synthesis.js +23 -2
- 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/native-messaging-audit.js +514 -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/pr-manager.js +47 -7
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/profile-router.js +16 -1
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/rule-validator.js +285 -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 +400 -43
- 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 +75 -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/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +434 -120
- package/.claude-plugin/README.md +0 -170
- package/adapters/README.md +0 -12
- 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
- package/skills/agent-memory/SKILL.md +0 -97
- package/skills/solve-architecture-autonomy/SKILL.md +0 -17
- package/skills/solve-architecture-autonomy/tool.js +0 -33
- package/skills/thumbgate-feedback/SKILL.md +0 -49
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { summarizePermissionTier } = require('./agent-readiness');
|
|
5
|
-
const { getOperationalDashboard } = require('./operational-dashboard');
|
|
6
|
-
|
|
7
|
-
const DISPATCH_TASK_DESCRIPTIONS = {
|
|
8
|
-
recall: 'Recall prior mistakes and prevention rules before planning.',
|
|
9
|
-
feedback_summary: 'Summarize recent wins, failures, and operator notes.',
|
|
10
|
-
search_lessons: 'Search promoted lessons and inspect what corrective action the system linked to each one.',
|
|
11
|
-
retrieve_lessons: 'Retrieve top-K relevant lessons for a given tool/action context (per-action guidance).',
|
|
12
|
-
search_thumbgate: 'Search raw ThumbGate feedback, ContextFS memory, and prevention rules.',
|
|
13
|
-
feedback_stats: 'Inspect approval trends and failure domains.',
|
|
14
|
-
diagnose_failure: 'Explain why a run failed or was blocked.',
|
|
15
|
-
list_intents: 'List available workflow plans without executing them.',
|
|
16
|
-
plan_intent: 'Plan a workflow with checkpoints and no execution.',
|
|
17
|
-
context_provenance: 'Audit recent context and evidence decisions.',
|
|
18
|
-
gate_stats: 'Review blocked and warned gate trends.',
|
|
19
|
-
dashboard: 'Summarize health, proof, gates, and pipeline metrics.',
|
|
20
|
-
get_business_metrics: 'Read revenue, conversion, and customer metrics.',
|
|
21
|
-
describe_semantic_entity: 'Explain Customer, Revenue, or Funnel state.',
|
|
22
|
-
enforcement_matrix: 'Full pipeline state: feedback counts, promotion rate, active gates, rejection ledger.',
|
|
23
|
-
get_reliability_rules: 'Review active prevention rules and success patterns.',
|
|
24
|
-
describe_reliability_entity: 'Alias for semantic entity definitions.',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const DISPATCH_BLOCKED_TASKS = [
|
|
28
|
-
'Direct code edits or git writes from the primary checkout.',
|
|
29
|
-
'Starting or completing handoffs from the remote session.',
|
|
30
|
-
'Memory writes, context-pack writes, or gate satisfaction mutations.',
|
|
31
|
-
'Admin-only billing or workflow mutation endpoints.',
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const DISPATCH_PROMPTS = [
|
|
35
|
-
'Summarize revenue, funnel, gates, and proof-backed workflow health for the last 7d.',
|
|
36
|
-
'Explain the top blocked gate and the repeated mistake it is preventing.',
|
|
37
|
-
'Plan the next workflow-hardening sprint for this repo without executing any changes.',
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
function buildDispatchBrief(data, options = {}) {
|
|
41
|
-
const profileName = String(options.profile || 'dispatch').trim() || 'dispatch';
|
|
42
|
-
const permissions = summarizePermissionTier(profileName);
|
|
43
|
-
const analytics = data.analytics || {};
|
|
44
|
-
const revenue = analytics.revenue || {};
|
|
45
|
-
const funnel = analytics.funnel || {};
|
|
46
|
-
const northStar = analytics.northStar || {};
|
|
47
|
-
const gateStats = data.gateStats || {};
|
|
48
|
-
const readiness = data.readiness || {};
|
|
49
|
-
const operational = data.operational || {};
|
|
50
|
-
|
|
51
|
-
const allowedTasks = permissions.allowedTools
|
|
52
|
-
.map((toolName) => ({
|
|
53
|
-
tool: toolName,
|
|
54
|
-
description: DISPATCH_TASK_DESCRIPTIONS[toolName],
|
|
55
|
-
}))
|
|
56
|
-
.filter((entry) => entry.description);
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
generatedAt: new Date().toISOString(),
|
|
60
|
-
source: options.source || operational.source || 'local',
|
|
61
|
-
fallbackReason: options.fallbackReason || operational.fallbackReason || null,
|
|
62
|
-
profile: permissions.profile,
|
|
63
|
-
tier: permissions.tier,
|
|
64
|
-
writeCapable: permissions.writeCapable,
|
|
65
|
-
readiness: {
|
|
66
|
-
overallStatus: readiness.overallStatus || 'unknown',
|
|
67
|
-
runtimeMode: readiness.runtime && readiness.runtime.mode ? readiness.runtime.mode : 'unknown',
|
|
68
|
-
bootstrapReady: Boolean(readiness.bootstrap && readiness.bootstrap.ready),
|
|
69
|
-
},
|
|
70
|
-
metrics: {
|
|
71
|
-
bookedRevenueUsd: Number(((Number(revenue.bookedRevenueCents || 0)) / 100).toFixed(2)),
|
|
72
|
-
paidOrders: revenue.paidOrders || 0,
|
|
73
|
-
uniqueLeads: funnel.uniqueLeads || 0,
|
|
74
|
-
visitors: funnel.visitors || 0,
|
|
75
|
-
checkoutStarts: funnel.checkoutStarts || 0,
|
|
76
|
-
weeklyProofBackedWorkflowRuns: northStar.weeklyActiveProofBackedWorkflowRuns || 0,
|
|
77
|
-
weeklyTeamsRunningProofBackedWorkflows: northStar.weeklyTeamsRunningProofBackedWorkflows || 0,
|
|
78
|
-
activeGates: gateStats.totalGates || 0,
|
|
79
|
-
blockedActions: gateStats.blocked || 0,
|
|
80
|
-
warnedActions: gateStats.warned || 0,
|
|
81
|
-
topBlockedGate: gateStats.topBlocked
|
|
82
|
-
? {
|
|
83
|
-
id: gateStats.topBlocked,
|
|
84
|
-
count: gateStats.topBlockedCount || 0,
|
|
85
|
-
}
|
|
86
|
-
: null,
|
|
87
|
-
},
|
|
88
|
-
allowedTasks,
|
|
89
|
-
blockedTasks: DISPATCH_BLOCKED_TASKS,
|
|
90
|
-
promptTemplates: DISPATCH_PROMPTS,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function formatDispatchBrief(brief) {
|
|
95
|
-
const lines = [];
|
|
96
|
-
lines.push('Dispatch Ops Brief');
|
|
97
|
-
lines.push('─'.repeat(40));
|
|
98
|
-
lines.push(`Source : ${brief.source}${brief.fallbackReason ? ` (${brief.fallbackReason})` : ''}`);
|
|
99
|
-
lines.push(`Profile : ${brief.profile} (${brief.tier})`);
|
|
100
|
-
lines.push(`Readiness : ${brief.readiness.overallStatus}`);
|
|
101
|
-
lines.push(`Runtime : ${brief.readiness.runtimeMode}`);
|
|
102
|
-
lines.push(`Bootstrap : ${brief.readiness.bootstrapReady ? 'ready' : 'missing context'}`);
|
|
103
|
-
lines.push('');
|
|
104
|
-
lines.push('Key Metrics');
|
|
105
|
-
lines.push(` Booked revenue : $${brief.metrics.bookedRevenueUsd.toFixed(2)}`);
|
|
106
|
-
lines.push(` Paid orders : ${brief.metrics.paidOrders}`);
|
|
107
|
-
lines.push(` Unique leads : ${brief.metrics.uniqueLeads}`);
|
|
108
|
-
lines.push(` Visitors : ${brief.metrics.visitors}`);
|
|
109
|
-
lines.push(` Checkout starts : ${brief.metrics.checkoutStarts}`);
|
|
110
|
-
lines.push(` Weekly proof runs : ${brief.metrics.weeklyProofBackedWorkflowRuns}`);
|
|
111
|
-
lines.push(` Weekly teams : ${brief.metrics.weeklyTeamsRunningProofBackedWorkflows}`);
|
|
112
|
-
lines.push(` Active gates : ${brief.metrics.activeGates}`);
|
|
113
|
-
lines.push(` Blocked actions : ${brief.metrics.blockedActions}`);
|
|
114
|
-
lines.push(` Warned actions : ${brief.metrics.warnedActions}`);
|
|
115
|
-
if (brief.metrics.topBlockedGate) {
|
|
116
|
-
lines.push(` Top blocked gate : ${brief.metrics.topBlockedGate.id} (${brief.metrics.topBlockedGate.count}x)`);
|
|
117
|
-
}
|
|
118
|
-
lines.push('');
|
|
119
|
-
lines.push('Safe Remote Tasks');
|
|
120
|
-
brief.allowedTasks.forEach((entry) => {
|
|
121
|
-
lines.push(`- ${entry.tool}: ${entry.description}`);
|
|
122
|
-
});
|
|
123
|
-
lines.push('');
|
|
124
|
-
lines.push('Do Not Do From Dispatch');
|
|
125
|
-
brief.blockedTasks.forEach((task) => {
|
|
126
|
-
lines.push(`- ${task}`);
|
|
127
|
-
});
|
|
128
|
-
lines.push('');
|
|
129
|
-
lines.push('Prompt Templates');
|
|
130
|
-
brief.promptTemplates.forEach((prompt) => {
|
|
131
|
-
lines.push(`- ${prompt}`);
|
|
132
|
-
});
|
|
133
|
-
return `${lines.join('\n')}\n`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function getDispatchBrief(options = {}) {
|
|
137
|
-
const profile = String(options.profile || 'dispatch').trim() || 'dispatch';
|
|
138
|
-
const { source, data, fallbackReason } = await getOperationalDashboard(options);
|
|
139
|
-
return buildDispatchBrief(data, { profile, source, fallbackReason });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
module.exports = {
|
|
143
|
-
DISPATCH_BLOCKED_TASKS,
|
|
144
|
-
DISPATCH_PROMPTS,
|
|
145
|
-
buildDispatchBrief,
|
|
146
|
-
formatDispatchBrief,
|
|
147
|
-
getDispatchBrief,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
if (require.main === module) {
|
|
151
|
-
getDispatchBrief()
|
|
152
|
-
.then((brief) => {
|
|
153
|
-
process.stdout.write(formatDispatchBrief(brief));
|
|
154
|
-
})
|
|
155
|
-
.catch((err) => {
|
|
156
|
-
console.error(err && err.message ? err.message : err);
|
|
157
|
-
process.exit(1);
|
|
158
|
-
});
|
|
159
|
-
}
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
|
|
6
|
-
const ROOT = path.join(__dirname, '..');
|
|
7
|
-
const PRODUCTHUNT_URL = 'https://www.producthunt.com/products/thumbgate';
|
|
8
|
-
const CLAUDE_PLUGIN_LATEST_ASSET_NAME = 'thumbgate-claude-desktop.mcpb';
|
|
9
|
-
const CLAUDE_PLUGIN_NEXT_ASSET_NAME = 'thumbgate-claude-desktop-next.mcpb';
|
|
10
|
-
const CLAUDE_PLUGIN_REVIEW_LATEST_ASSET_NAME = 'thumbgate-claude-plugin-review.zip';
|
|
11
|
-
const CLAUDE_PLUGIN_REVIEW_NEXT_ASSET_NAME = 'thumbgate-claude-plugin-review-next.zip';
|
|
12
|
-
const CODEX_PLUGIN_LATEST_ASSET_NAME = 'thumbgate-codex-plugin.zip';
|
|
13
|
-
const CODEX_PLUGIN_NEXT_ASSET_NAME = 'thumbgate-codex-plugin-next.zip';
|
|
14
|
-
|
|
15
|
-
function readJson(root, relativePath) {
|
|
16
|
-
return JSON.parse(fs.readFileSync(path.join(root, relativePath), 'utf8'));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function getPackageVersion(root = ROOT) {
|
|
20
|
-
return String(readJson(root, 'package.json').version || '').trim();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getRepositoryUrl(root = ROOT) {
|
|
24
|
-
return String(readJson(root, 'package.json').repository.url || '').replace(/\.git$/, '');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getClaudePluginVersionedAssetName(version = getPackageVersion(ROOT)) {
|
|
28
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
29
|
-
return `thumbgate-claude-desktop-v${normalized}.mcpb`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function getClaudePluginReviewVersionedAssetName(version = getPackageVersion(ROOT)) {
|
|
33
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
34
|
-
return `thumbgate-claude-plugin-review-v${normalized}.zip`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isPrereleaseVersion(version = getPackageVersion(ROOT)) {
|
|
38
|
-
return /^\d+\.\d+\.\d+-[0-9A-Za-z.-]+$/.test(String(version || '').trim());
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function getClaudePluginChannelAssetName(version = getPackageVersion(ROOT)) {
|
|
42
|
-
return isPrereleaseVersion(version) ? CLAUDE_PLUGIN_NEXT_ASSET_NAME : CLAUDE_PLUGIN_LATEST_ASSET_NAME;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getClaudePluginReviewChannelAssetName(version = getPackageVersion(ROOT)) {
|
|
46
|
-
return isPrereleaseVersion(version)
|
|
47
|
-
? CLAUDE_PLUGIN_REVIEW_NEXT_ASSET_NAME
|
|
48
|
-
: CLAUDE_PLUGIN_REVIEW_LATEST_ASSET_NAME;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function getClaudePluginLatestDownloadUrl(root = ROOT) {
|
|
52
|
-
return `${getRepositoryUrl(root)}/releases/latest/download/${CLAUDE_PLUGIN_LATEST_ASSET_NAME}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getClaudePluginVersionedDownloadUrl(root = ROOT, version = getPackageVersion(root)) {
|
|
56
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
57
|
-
return `${getRepositoryUrl(root)}/releases/download/v${normalized}/${getClaudePluginVersionedAssetName(normalized)}`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function getClaudePluginReviewLatestDownloadUrl(root = ROOT) {
|
|
61
|
-
return `${getRepositoryUrl(root)}/releases/latest/download/${CLAUDE_PLUGIN_REVIEW_LATEST_ASSET_NAME}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function getClaudePluginReviewVersionedDownloadUrl(root = ROOT, version = getPackageVersion(root)) {
|
|
65
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
66
|
-
return `${getRepositoryUrl(root)}/releases/download/v${normalized}/${getClaudePluginReviewVersionedAssetName(normalized)}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function getCodexPluginVersionedAssetName(version = getPackageVersion(ROOT)) {
|
|
70
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
71
|
-
return `thumbgate-codex-plugin-v${normalized}.zip`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function getCodexPluginChannelAssetName(version = getPackageVersion(ROOT)) {
|
|
75
|
-
return isPrereleaseVersion(version) ? CODEX_PLUGIN_NEXT_ASSET_NAME : CODEX_PLUGIN_LATEST_ASSET_NAME;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function getCodexPluginLatestDownloadUrl(root = ROOT) {
|
|
79
|
-
return `${getRepositoryUrl(root)}/releases/latest/download/${CODEX_PLUGIN_LATEST_ASSET_NAME}`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function getCodexPluginVersionedDownloadUrl(root = ROOT, version = getPackageVersion(root)) {
|
|
83
|
-
const normalized = String(version || '').replace(/^v/, '');
|
|
84
|
-
return `${getRepositoryUrl(root)}/releases/download/v${normalized}/${getCodexPluginVersionedAssetName(normalized)}`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
module.exports = {
|
|
88
|
-
CLAUDE_PLUGIN_LATEST_ASSET_NAME,
|
|
89
|
-
CLAUDE_PLUGIN_NEXT_ASSET_NAME,
|
|
90
|
-
CLAUDE_PLUGIN_REVIEW_LATEST_ASSET_NAME,
|
|
91
|
-
CLAUDE_PLUGIN_REVIEW_NEXT_ASSET_NAME,
|
|
92
|
-
CODEX_PLUGIN_LATEST_ASSET_NAME,
|
|
93
|
-
CODEX_PLUGIN_NEXT_ASSET_NAME,
|
|
94
|
-
PRODUCTHUNT_URL,
|
|
95
|
-
getClaudePluginChannelAssetName,
|
|
96
|
-
getClaudePluginLatestDownloadUrl,
|
|
97
|
-
getClaudePluginReviewChannelAssetName,
|
|
98
|
-
getClaudePluginReviewLatestDownloadUrl,
|
|
99
|
-
getClaudePluginReviewVersionedAssetName,
|
|
100
|
-
getClaudePluginReviewVersionedDownloadUrl,
|
|
101
|
-
getClaudePluginVersionedAssetName,
|
|
102
|
-
getClaudePluginVersionedDownloadUrl,
|
|
103
|
-
getCodexPluginChannelAssetName,
|
|
104
|
-
getCodexPluginLatestDownloadUrl,
|
|
105
|
-
getCodexPluginVersionedAssetName,
|
|
106
|
-
getCodexPluginVersionedDownloadUrl,
|
|
107
|
-
getPackageVersion,
|
|
108
|
-
getRepositoryUrl,
|
|
109
|
-
isPrereleaseVersion,
|
|
110
|
-
};
|
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { resolveFeedbackDir: resolveSharedFeedbackDir } = require('./feedback-paths');
|
|
6
|
-
const { readJsonlTail } = require('./fs-utils');
|
|
7
|
-
|
|
8
|
-
const DEFAULT_HISTORY_LIMIT = 10;
|
|
9
|
-
|
|
10
|
-
const CORRECTION_PATTERNS = [
|
|
11
|
-
/\bdon['’]?t\b/i,
|
|
12
|
-
/\bdo not\b/i,
|
|
13
|
-
/\bnever\b/i,
|
|
14
|
-
/\bavoid\b/i,
|
|
15
|
-
/\bstop\b/i,
|
|
16
|
-
/\bmust\b/i,
|
|
17
|
-
/\bshould\b/i,
|
|
18
|
-
/\bneed to\b/i,
|
|
19
|
-
/\buse\b/i,
|
|
20
|
-
/\brun tests?\b/i,
|
|
21
|
-
/\binclude\b/i,
|
|
22
|
-
/\bprove\b/i,
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const FAILURE_PATTERNS = [
|
|
26
|
-
/\bfailed\b/i,
|
|
27
|
-
/\bbroke\b/i,
|
|
28
|
-
/\berror\b/i,
|
|
29
|
-
/\bwrong\b/i,
|
|
30
|
-
/\bignored\b/i,
|
|
31
|
-
/\bskipped\b/i,
|
|
32
|
-
/\bhallucinat/i,
|
|
33
|
-
/\bclaimed done\b/i,
|
|
34
|
-
/\bwithout proof\b/i,
|
|
35
|
-
/\bwithout evidence\b/i,
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
const SUCCESS_PATTERNS = [
|
|
39
|
-
/\bworked\b/i,
|
|
40
|
-
/\bpassed\b/i,
|
|
41
|
-
/\bverified\b/i,
|
|
42
|
-
/\bproof\b/i,
|
|
43
|
-
/\bevidence\b/i,
|
|
44
|
-
/\btests?\b/i,
|
|
45
|
-
/\bfixed\b/i,
|
|
46
|
-
/\bsuccess/i,
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
function normalizeText(value) {
|
|
50
|
-
return String(value || '')
|
|
51
|
-
.replace(/\s+/g, ' ')
|
|
52
|
-
.trim();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function truncate(value, max = 180) {
|
|
56
|
-
const text = normalizeText(value);
|
|
57
|
-
if (text.length <= max) return text;
|
|
58
|
-
return `${text.slice(0, max - 1).trim()}…`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function appendJsonl(filePath, record) {
|
|
62
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
63
|
-
fs.appendFileSync(filePath, `${JSON.stringify(record)}\n`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function resolveFeedbackDir(feedbackDir) {
|
|
67
|
-
if (feedbackDir) {
|
|
68
|
-
return resolveSharedFeedbackDir({ feedbackDir });
|
|
69
|
-
}
|
|
70
|
-
const env = { ...process.env };
|
|
71
|
-
delete env.INIT_CWD;
|
|
72
|
-
delete env.PWD;
|
|
73
|
-
return resolveSharedFeedbackDir({ env });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function getConversationPaths(feedbackDir) {
|
|
77
|
-
const resolved = resolveFeedbackDir(feedbackDir);
|
|
78
|
-
return {
|
|
79
|
-
feedbackDir: resolved,
|
|
80
|
-
conversationLogPath: path.join(resolved, 'conversation-window.jsonl'),
|
|
81
|
-
feedbackLogPath: path.join(resolved, 'feedback-log.jsonl'),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function normalizeChatHistory(entries = []) {
|
|
86
|
-
if (!Array.isArray(entries)) return [];
|
|
87
|
-
return entries
|
|
88
|
-
.map((entry) => {
|
|
89
|
-
if (typeof entry === 'string') {
|
|
90
|
-
const text = normalizeText(entry);
|
|
91
|
-
return text ? { author: null, text, timestamp: null, source: 'chat_history' } : null;
|
|
92
|
-
}
|
|
93
|
-
if (!entry || typeof entry !== 'object') return null;
|
|
94
|
-
const text = normalizeText(entry.text || entry.body || entry.message || entry.content);
|
|
95
|
-
if (!text) return null;
|
|
96
|
-
return {
|
|
97
|
-
author: normalizeText(entry.author || entry.role || entry.user || entry.name) || null,
|
|
98
|
-
text,
|
|
99
|
-
timestamp: normalizeText(entry.timestamp || entry.createdAt || entry.updatedAt) || null,
|
|
100
|
-
source: normalizeText(entry.source) || 'chat_history',
|
|
101
|
-
};
|
|
102
|
-
})
|
|
103
|
-
.filter(Boolean);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function recordConversationEntry(entry, options = {}) {
|
|
107
|
-
const { conversationLogPath } = getConversationPaths(options.feedbackDir);
|
|
108
|
-
const normalized = normalizeChatHistory([entry])[0];
|
|
109
|
-
if (!normalized) {
|
|
110
|
-
return { recorded: false, reason: 'empty_text', conversationLogPath };
|
|
111
|
-
}
|
|
112
|
-
const record = {
|
|
113
|
-
...normalized,
|
|
114
|
-
timestamp: normalized.timestamp || new Date().toISOString(),
|
|
115
|
-
};
|
|
116
|
-
appendJsonl(conversationLogPath, record);
|
|
117
|
-
return { recorded: true, record, conversationLogPath };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function readRecentConversationWindow(options = {}) {
|
|
121
|
-
const limit = Number(options.limit || DEFAULT_HISTORY_LIMIT);
|
|
122
|
-
const { conversationLogPath } = getConversationPaths(options.feedbackDir);
|
|
123
|
-
return readJsonlTail(conversationLogPath, limit)
|
|
124
|
-
.map((entry) => normalizeChatHistory([entry])[0])
|
|
125
|
-
.filter(Boolean);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function findFeedbackEventById(feedbackId, options = {}) {
|
|
129
|
-
if (!feedbackId) return null;
|
|
130
|
-
const { feedbackLogPath } = getConversationPaths(options.feedbackDir);
|
|
131
|
-
const matches = readJsonlTail(feedbackLogPath, Number(options.searchLimit || 200));
|
|
132
|
-
for (let index = matches.length - 1; index >= 0; index -= 1) {
|
|
133
|
-
if (matches[index] && matches[index].id === feedbackId) {
|
|
134
|
-
return matches[index];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function buildLastActionMessage(lastAction) {
|
|
141
|
-
if (!lastAction || typeof lastAction !== 'object') return null;
|
|
142
|
-
const tool = normalizeText(lastAction.tool || lastAction.tool_name || 'unknown tool');
|
|
143
|
-
const file = normalizeText(lastAction.file || lastAction.path || '');
|
|
144
|
-
const detail = file ? `${tool} on ${file}` : tool;
|
|
145
|
-
return {
|
|
146
|
-
author: 'tool',
|
|
147
|
-
text: `Last action: ${detail}`,
|
|
148
|
-
timestamp: normalizeText(lastAction.timestamp) || null,
|
|
149
|
-
source: 'last_action',
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function buildRelatedFeedbackMessages(feedbackEvent) {
|
|
154
|
-
if (!feedbackEvent || typeof feedbackEvent !== 'object') return [];
|
|
155
|
-
const messages = [];
|
|
156
|
-
|
|
157
|
-
if (feedbackEvent.conversationWindow && Array.isArray(feedbackEvent.conversationWindow)) {
|
|
158
|
-
for (const entry of normalizeChatHistory(feedbackEvent.conversationWindow)) {
|
|
159
|
-
messages.push({ ...entry, source: 'related_feedback_window' });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const snippets = [
|
|
164
|
-
feedbackEvent.submittedContext || null,
|
|
165
|
-
feedbackEvent.context || null,
|
|
166
|
-
feedbackEvent.whatWentWrong || null,
|
|
167
|
-
feedbackEvent.whatWorked || null,
|
|
168
|
-
feedbackEvent.whatToChange || null,
|
|
169
|
-
].filter(Boolean);
|
|
170
|
-
|
|
171
|
-
for (const text of snippets) {
|
|
172
|
-
messages.push({
|
|
173
|
-
author: 'related-feedback',
|
|
174
|
-
text: normalizeText(text),
|
|
175
|
-
timestamp: normalizeText(feedbackEvent.timestamp) || null,
|
|
176
|
-
source: 'related_feedback',
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const lastActionMessage = buildLastActionMessage(feedbackEvent.lastAction);
|
|
181
|
-
if (lastActionMessage) messages.push(lastActionMessage);
|
|
182
|
-
|
|
183
|
-
return messages;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function matchesAny(text, patterns) {
|
|
187
|
-
return patterns.some((pattern) => pattern.test(text));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function chooseMessage(messages, predicate) {
|
|
191
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
192
|
-
if (predicate(messages[index])) return messages[index];
|
|
193
|
-
}
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function buildRuleSuggestion(correctionMessage, signal) {
|
|
198
|
-
if (!correctionMessage) return null;
|
|
199
|
-
const text = truncate(correctionMessage.text, 120);
|
|
200
|
-
if (signal === 'negative' && /\bnever\b/i.test(text)) return text;
|
|
201
|
-
if (signal === 'negative' && /\bdo not\b/i.test(text)) {
|
|
202
|
-
return text.replace(/\bdo not\b/i, 'Never');
|
|
203
|
-
}
|
|
204
|
-
if (signal === 'negative' && /\bdon['’]?t\b/i.test(text)) {
|
|
205
|
-
return text
|
|
206
|
-
.replace(/\bdon['’]?t\b/i, 'Never')
|
|
207
|
-
.replace(/\bdo not\b/i, 'Never');
|
|
208
|
-
}
|
|
209
|
-
if (signal === 'negative' && /\bavoid\b/i.test(text)) {
|
|
210
|
-
return text.replace(/\bavoid\b/i, 'Avoid');
|
|
211
|
-
}
|
|
212
|
-
if (signal === 'negative') return `Follow the earlier instruction: ${text}`;
|
|
213
|
-
return `Repeat the successful pattern: ${text}`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function inferNegativeDistillation(messages, params) {
|
|
217
|
-
const correctionMessage = chooseMessage(messages, (entry) => {
|
|
218
|
-
const text = normalizeText(entry.text);
|
|
219
|
-
return Boolean(text) && matchesAny(text, CORRECTION_PATTERNS);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const failureMessage = chooseMessage(messages, (entry) => {
|
|
223
|
-
const text = normalizeText(entry.text);
|
|
224
|
-
return Boolean(text) && matchesAny(text, FAILURE_PATTERNS);
|
|
225
|
-
}) || buildLastActionMessage(params.lastAction);
|
|
226
|
-
|
|
227
|
-
if (!correctionMessage && !failureMessage) {
|
|
228
|
-
return {
|
|
229
|
-
usedHistory: false,
|
|
230
|
-
inferredFields: {},
|
|
231
|
-
lessonProposal: null,
|
|
232
|
-
evidence: [],
|
|
233
|
-
source: 'none',
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const evidence = [correctionMessage, failureMessage].filter(Boolean).map((entry) => truncate(entry.text));
|
|
238
|
-
const whatWentWrong = correctionMessage
|
|
239
|
-
? `It ignored a prior instruction: ${truncate(correctionMessage.text, 140)}`
|
|
240
|
-
: `The failure centered on: ${truncate(failureMessage.text, 140)}`;
|
|
241
|
-
const whatToChange = correctionMessage
|
|
242
|
-
? `Follow the earlier instruction instead of repeating the same pattern: ${truncate(correctionMessage.text, 140)}`
|
|
243
|
-
: failureMessage
|
|
244
|
-
? `Inspect and correct the failing step before repeating it: ${truncate(failureMessage.text, 140)}`
|
|
245
|
-
: null;
|
|
246
|
-
const context = failureMessage
|
|
247
|
-
? `History-aware distillation linked this failure to ${truncate(failureMessage.text, 140)}`
|
|
248
|
-
: `History-aware distillation linked this failure to ${truncate(correctionMessage.text, 140)}`;
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
usedHistory: true,
|
|
252
|
-
source: correctionMessage ? correctionMessage.source : failureMessage.source,
|
|
253
|
-
inferredFields: {
|
|
254
|
-
context,
|
|
255
|
-
whatWentWrong,
|
|
256
|
-
whatToChange,
|
|
257
|
-
},
|
|
258
|
-
lessonProposal: {
|
|
259
|
-
summary: whatWentWrong,
|
|
260
|
-
proposedRule: buildRuleSuggestion(correctionMessage, 'negative'),
|
|
261
|
-
confidence: correctionMessage && failureMessage ? 0.92 : 0.78,
|
|
262
|
-
},
|
|
263
|
-
evidence,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function inferPositiveDistillation(messages) {
|
|
268
|
-
const successMessage = chooseMessage(messages, (entry) => {
|
|
269
|
-
const text = normalizeText(entry.text);
|
|
270
|
-
return Boolean(text) && matchesAny(text, SUCCESS_PATTERNS);
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
if (!successMessage) {
|
|
274
|
-
return {
|
|
275
|
-
usedHistory: false,
|
|
276
|
-
inferredFields: {},
|
|
277
|
-
lessonProposal: null,
|
|
278
|
-
evidence: [],
|
|
279
|
-
source: 'none',
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const whatWorked = `The successful pattern was: ${truncate(successMessage.text, 140)}`;
|
|
284
|
-
return {
|
|
285
|
-
usedHistory: true,
|
|
286
|
-
source: successMessage.source,
|
|
287
|
-
inferredFields: {
|
|
288
|
-
context: `History-aware distillation found a successful pattern in recent conversation: ${truncate(successMessage.text, 140)}`,
|
|
289
|
-
whatWorked,
|
|
290
|
-
},
|
|
291
|
-
lessonProposal: {
|
|
292
|
-
summary: whatWorked,
|
|
293
|
-
proposedRule: buildRuleSuggestion(successMessage, 'positive'),
|
|
294
|
-
confidence: 0.81,
|
|
295
|
-
},
|
|
296
|
-
evidence: [truncate(successMessage.text)],
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function distillFeedbackHistory(params = {}) {
|
|
301
|
-
const signal = String(params.signal || '').toLowerCase().trim();
|
|
302
|
-
const historyLimit = Number(params.historyLimit || DEFAULT_HISTORY_LIMIT);
|
|
303
|
-
const explicitHistory = normalizeChatHistory(params.chatHistory || params.messages || []);
|
|
304
|
-
const fallbackHistory = params.allowLocalConversationFallback
|
|
305
|
-
? readRecentConversationWindow({ feedbackDir: params.feedbackDir, limit: historyLimit })
|
|
306
|
-
: [];
|
|
307
|
-
const relatedEvent = findFeedbackEventById(params.relatedFeedbackId, {
|
|
308
|
-
feedbackDir: params.feedbackDir,
|
|
309
|
-
searchLimit: params.searchLimit,
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
const conversationWindow = [
|
|
313
|
-
...explicitHistory,
|
|
314
|
-
...(explicitHistory.length === 0 ? fallbackHistory : []),
|
|
315
|
-
...buildRelatedFeedbackMessages(relatedEvent),
|
|
316
|
-
]
|
|
317
|
-
.filter((entry) => entry && normalizeText(entry.text))
|
|
318
|
-
.slice(-historyLimit);
|
|
319
|
-
|
|
320
|
-
const distillation = signal === 'negative'
|
|
321
|
-
? inferNegativeDistillation(conversationWindow, params)
|
|
322
|
-
: inferPositiveDistillation(conversationWindow, params);
|
|
323
|
-
|
|
324
|
-
return {
|
|
325
|
-
usedHistory: distillation.usedHistory,
|
|
326
|
-
source: explicitHistory.length > 0
|
|
327
|
-
? 'chat_history'
|
|
328
|
-
: fallbackHistory.length > 0
|
|
329
|
-
? 'local_conversation_window'
|
|
330
|
-
: relatedEvent ? 'related_feedback' : 'none',
|
|
331
|
-
conversationWindow,
|
|
332
|
-
relatedFeedbackId: relatedEvent ? relatedEvent.id : null,
|
|
333
|
-
inferredFields: distillation.inferredFields,
|
|
334
|
-
lessonProposal: distillation.lessonProposal,
|
|
335
|
-
evidence: distillation.evidence,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function main(argv = process.argv.slice(2)) {
|
|
340
|
-
const [command, ...rest] = argv;
|
|
341
|
-
if (command !== 'record') {
|
|
342
|
-
console.error('Usage: node scripts/feedback-history-distiller.js record --author=user --text="..."');
|
|
343
|
-
process.exit(1);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const args = {};
|
|
347
|
-
for (const token of rest) {
|
|
348
|
-
if (!token.startsWith('--')) continue;
|
|
349
|
-
const [key, ...valueParts] = token.slice(2).split('=');
|
|
350
|
-
args[key] = valueParts.join('=');
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const result = recordConversationEntry({
|
|
354
|
-
author: args.author || null,
|
|
355
|
-
text: args.text || '',
|
|
356
|
-
timestamp: args.timestamp || new Date().toISOString(),
|
|
357
|
-
source: args.source || 'cli_record',
|
|
358
|
-
}, {
|
|
359
|
-
feedbackDir: args.feedbackDir,
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
if (!result.recorded) {
|
|
363
|
-
console.error(result.reason || 'failed_to_record');
|
|
364
|
-
process.exit(1);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
process.stdout.write(JSON.stringify(result, null, 2));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (require.main === module) {
|
|
371
|
-
main();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
module.exports = {
|
|
375
|
-
DEFAULT_HISTORY_LIMIT,
|
|
376
|
-
distillFeedbackHistory,
|
|
377
|
-
findFeedbackEventById,
|
|
378
|
-
getConversationPaths,
|
|
379
|
-
normalizeChatHistory,
|
|
380
|
-
readRecentConversationWindow,
|
|
381
|
-
recordConversationEntry,
|
|
382
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { getBusinessAnalytics } = require('./billing');
|
|
3
|
-
|
|
4
|
-
function generateFunnelReport() {
|
|
5
|
-
const analytics = getBusinessAnalytics();
|
|
6
|
-
console.log('╔══════════════════════════════════════════════════════╗');
|
|
7
|
-
console.log('║ Marketing & Revenue Funnel Analytics ║');
|
|
8
|
-
console.log('╠══════════════════════════════════════════════════════╣');
|
|
9
|
-
console.log(`║ Traffic Visitors: ${String((analytics.trafficMetrics && analytics.trafficMetrics.visitors) || 0).padStart(6)} ║`);
|
|
10
|
-
console.log(`║ CTA Clicks: ${String((analytics.trafficMetrics && analytics.trafficMetrics.ctaClicks) || 0).padStart(6)} ║`);
|
|
11
|
-
console.log(`║ Unique Leads: ${String(analytics.signups.uniqueLeads).padStart(6)} ║`);
|
|
12
|
-
console.log(`║ Sprint Leads: ${String((analytics.pipeline.workflowSprintLeads && analytics.pipeline.workflowSprintLeads.total) || 0).padStart(6)} ║`);
|
|
13
|
-
console.log(`║ Paid Provider Events: ${String(analytics.revenue.paidProviderEvents || 0).padStart(6)} ║`);
|
|
14
|
-
console.log(`║ Paid Orders Tracked: ${String(analytics.revenue.paidOrders).padStart(6)} ║`);
|
|
15
|
-
console.log(`║ Known Booked Revenue (cents): ${String(analytics.revenue.bookedRevenueCents).padStart(6)} ║`);
|
|
16
|
-
console.log('╠══════════════════════════════════════════════════════╣');
|
|
17
|
-
console.log('║ Acquisition by Source: ║');
|
|
18
|
-
Object.entries(analytics.signups.bySource).forEach(([key, count]) => {
|
|
19
|
-
const line = ` ${key}: ${count}`;
|
|
20
|
-
console.log(`║ ${line.padEnd(52)}║`);
|
|
21
|
-
});
|
|
22
|
-
console.log('║ Paid Orders by Source: ║');
|
|
23
|
-
Object.entries(analytics.attribution.paidBySource).forEach(([key, count]) => {
|
|
24
|
-
const line = ` ${key}: ${count}`;
|
|
25
|
-
console.log(`║ ${line.padEnd(52)}║`);
|
|
26
|
-
});
|
|
27
|
-
console.log('║ Revenue by Source (cents): ║');
|
|
28
|
-
Object.entries(analytics.attribution.bookedRevenueBySourceCents).forEach(([key, count]) => {
|
|
29
|
-
const line = ` ${key}: ${count}`;
|
|
30
|
-
console.log(`║ ${line.padEnd(52)}║`);
|
|
31
|
-
});
|
|
32
|
-
console.log('╚══════════════════════════════════════════════════════╝');
|
|
33
|
-
}
|
|
34
|
-
if (require.main === module) generateFunnelReport();
|
|
35
|
-
module.exports = { generateFunnelReport };
|