rlhf-feedback-loop 0.6.8 → 0.6.10
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/README.md +45 -31
- package/adapters/chatgpt/openapi.yaml +124 -2
- package/adapters/mcp/server-stdio.js +84 -25
- package/bin/cli.js +34 -3
- package/openapi/openapi.yaml +124 -2
- package/package.json +14 -9
- package/scripts/billing.js +349 -89
- package/scripts/prove-adapters.js +135 -5
- package/scripts/prove-subway-upgrades.js +28 -1
- package/src/api/server.js +58 -24
package/README.md
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
|
-
#
|
|
1
|
+
# RLHF-Ready Feedback Loop — Agentic Control Plane & Context Engineering Studio
|
|
2
2
|
|
|
3
3
|
[](https://github.com/IgorGanapolsky/rlhf-feedback-loop/actions/workflows/ci.yml)
|
|
4
4
|
[](docs/ANTHROPIC_MARKETPLACE_STRATEGY.md)
|
|
5
|
-
[](docs/geo-strategy-for-ai-agents.md)
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Stop Vibe Coding. Start Context Engineering.** The RLHF-Ready Feedback Loop is the enterprise-grade **Agentic Control Plane** for AI workflows. We provide the operational layer to capture human preference signals, engineer high-density context packs, and enforce machine-readable guardrails to stop your agents from going "off-script."
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Most AI agents run on "vibes." We provide the infrastructure to convert those vibes into **Hard Evidence** for continuous improvement.
|
|
12
|
-
|
|
13
|
-
- **Veto Layer (Governance):** Convert subjective user feedback into non-bypassable architectural constraints (`CLAUDE.md`).
|
|
14
|
-
- **RLHF-Ready Datasets:** Automatically generate high-density DPO (Direct Preference Optimization) pairs from real-world agent interactions.
|
|
15
|
-
- **Online Bayesian Reward Estimation:** Uses Thompson Sampling to model user preferences in real-time, providing a local "Reward Signal" without heavy training.
|
|
9
|
+
This product captures and structures human feedback data for optimization workflows. It is **RLHF-ready data infrastructure** (not an end-to-end reward-model + RL fine-tuning trainer by itself).
|
|
16
10
|
|
|
17
11
|
## True Plug-and-Play: Zero-Config Integration
|
|
18
12
|
|
|
19
|
-
The Feedback
|
|
13
|
+
The RLHF Feedback Loop is now a **Universal Agent Skill**. You can drop it into any repository without manual setup.
|
|
20
14
|
|
|
21
15
|
- **Zero-Config Discovery:** Automatically detects project context. If no local `.rlhf/` directory exists, it safely fallbacks to a project-scoped global store in `~/.rlhf/`.
|
|
22
|
-
- **Global Skill Installation:**
|
|
16
|
+
- **Global Skill Installation (Optional):** One-command installer is available if you want auto-detection.
|
|
17
|
+
- **Vibe-to-Verification (V2V):** Directly converts subjective "vibes" (thumbs up/down) into verifiable repository rules (`CLAUDE.md`).
|
|
18
|
+
|
|
19
|
+
### Quick Start (Stable MCP Commands)
|
|
20
|
+
|
|
21
|
+
Add the MCP server directly in your client config:
|
|
22
|
+
|
|
23
|
+
| Platform | Command |
|
|
24
|
+
|----------|---------|
|
|
25
|
+
| **Claude** | `claude mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
26
|
+
| **Codex** | `codex mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
27
|
+
| **Gemini** | `gemini mcp add rlhf "npx -y rlhf-feedback-loop serve"` |
|
|
28
|
+
| **Amp** | `amp mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
29
|
+
| **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
Optional auto-installer:
|
|
25
32
|
|
|
26
33
|
```bash
|
|
27
|
-
npx rlhf-feedback-loop
|
|
34
|
+
npx add-mcp rlhf-feedback-loop
|
|
28
35
|
```
|
|
29
36
|
|
|
30
|
-
|
|
37
|
+
- **Stop Regressions:** Automatically convert negative feedback into `CLAUDE.md` / `AGENTS.md` prevention rules.
|
|
38
|
+
- **Preference Data Engineering:** Capture high-density context (rubrics, guardrails, metadata) for DPO training.
|
|
39
|
+
- **Bayesian Scoring:** Use Thompson Sampling to handle evolving user preferences over time.
|
|
31
40
|
|
|
32
41
|
## Use Cases
|
|
33
42
|
|
|
34
43
|
- **Automated Code Reviews:** Capture PR feedback to enforce team-specific style guides autonomously.
|
|
35
|
-
- **Self-Healing Multi-Agent Systems:** Share
|
|
36
|
-
- **
|
|
44
|
+
- **Self-Healing Multi-Agent Systems:** Share prevention rules across a swarm of agents to avoid systemic bottlenecks.
|
|
45
|
+
- **Custom LLM Fine-tuning:** Collect proprietary preference data to train smaller, faster models that perform like GPT-4 on your specific codebase.
|
|
37
46
|
|
|
38
|
-
##
|
|
47
|
+
## Generative Engine Optimization (GEO)
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
We are optimized for discovery by next-gen AI tools (Claude Code, Gemini CLI, Perplexity). See our [GEO Strategy](docs/geo-strategy-for-ai-agents.md) for how we use **Context-First Publishing** to remain the first-choice recommendation for agent reliability.
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
| **Codex** | `codex mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
46
|
-
| **Gemini** | `gemini mcp add rlhf "npx -y rlhf-feedback-loop serve"` |
|
|
47
|
-
| **Amp** | `amp mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
48
|
-
| **Cursor** | `cursor mcp add rlhf -- npx -y rlhf-feedback-loop serve` |
|
|
51
|
+
## Get Started
|
|
52
|
+
|
|
53
|
+
Run one `mcp add` command for your client. The server starts on each session and can capture feedback, recall past learnings, and block repeated mistakes.
|
|
49
54
|
|
|
50
55
|
## How It Works
|
|
51
56
|
|
|
52
57
|
```
|
|
53
|
-
|
|
58
|
+
Thumbs up/down
|
|
54
59
|
|
|
|
55
60
|
v
|
|
56
61
|
Capture → JSONL log
|
|
@@ -63,14 +68,14 @@ Subjective Signal (Vibe)
|
|
|
63
68
|
Good Bad
|
|
64
69
|
| |
|
|
65
70
|
v v
|
|
66
|
-
Learn
|
|
71
|
+
Learn Prevention rule
|
|
67
72
|
| |
|
|
68
73
|
v v
|
|
69
74
|
LanceDB ShieldCortex
|
|
70
75
|
vectors context packs
|
|
71
76
|
|
|
|
72
77
|
v
|
|
73
|
-
DPO export →
|
|
78
|
+
DPO export → fine-tune your model
|
|
74
79
|
```
|
|
75
80
|
|
|
76
81
|
All data stored locally as **JSONL** files — fully transparent, fully portable, no vendor lock-in. **LanceDB** indexes memories as vector embeddings for semantic search. **ShieldCortex** assembles context packs so your agent starts each task informed.
|
|
@@ -79,15 +84,24 @@ All data stored locally as **JSONL** files — fully transparent, fully portable
|
|
|
79
84
|
|
|
80
85
|
The open-source package is fully functional and free forever. Cloud Pro is for teams that don't want to self-host.
|
|
81
86
|
|
|
82
|
-
| | Open Source | Cloud Pro ($
|
|
87
|
+
| | Open Source | Cloud Pro ($49/mo) |
|
|
83
88
|
|---|---|---|
|
|
84
89
|
| Feedback capture | Local MCP server | Hosted HTTPS API |
|
|
85
90
|
| Storage | Your machine | Managed cloud |
|
|
86
91
|
| DPO export | CLI command | API endpoint |
|
|
87
92
|
| Setup | `mcp add` one-liner | Provisioned API key |
|
|
88
93
|
| Team sharing | Manual (share JSONL) | Built-in (shared API) |
|
|
94
|
+
| Support | GitHub Issues | Email |
|
|
95
|
+
| Uptime | You manage | We manage (99.9% SLA) |
|
|
96
|
+
|
|
97
|
+
[Get Cloud Pro](https://buy.stripe.com/bJe14neyU4r4f0leOD3sI02) | [Live API](https://rlhf-feedback-loop-710216278770.us-central1.run.app) | [Verification Evidence](docs/VERIFICATION_EVIDENCE.md)
|
|
98
|
+
|
|
99
|
+
## Deep Dive
|
|
89
100
|
|
|
90
|
-
[
|
|
101
|
+
- [API Reference](openapi/openapi.yaml) — full OpenAPI spec
|
|
102
|
+
- [Context Engine](docs/CONTEXTFS.md) — multi-agent memory orchestration
|
|
103
|
+
- [Autonomous GitOps](docs/AUTONOMOUS_GITOPS.md) — self-healing CI/CD
|
|
104
|
+
- [Contributing](CONTRIBUTING.md)
|
|
91
105
|
|
|
92
106
|
## License
|
|
93
107
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: RLHF Feedback Loop API
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
description: |
|
|
6
6
|
Production API for feedback capture, schema-validated memory promotion,
|
|
7
7
|
prevention rule generation, and DPO export.
|
|
8
8
|
servers:
|
|
9
|
-
- url:
|
|
9
|
+
- url: https://rlhf-feedback-loop-710216278770.us-central1.run.app
|
|
10
10
|
security:
|
|
11
11
|
- bearerAuth: []
|
|
12
12
|
components:
|
|
@@ -80,6 +80,59 @@ components:
|
|
|
80
80
|
type: string
|
|
81
81
|
approved:
|
|
82
82
|
type: boolean
|
|
83
|
+
BillingCheckoutRequest:
|
|
84
|
+
type: object
|
|
85
|
+
properties:
|
|
86
|
+
successUrl:
|
|
87
|
+
type: string
|
|
88
|
+
format: uri
|
|
89
|
+
cancelUrl:
|
|
90
|
+
type: string
|
|
91
|
+
format: uri
|
|
92
|
+
customerEmail:
|
|
93
|
+
type: string
|
|
94
|
+
format: email
|
|
95
|
+
installId:
|
|
96
|
+
type: string
|
|
97
|
+
metadata:
|
|
98
|
+
type: object
|
|
99
|
+
additionalProperties:
|
|
100
|
+
type: string
|
|
101
|
+
BillingProvisionRequest:
|
|
102
|
+
type: object
|
|
103
|
+
required: [customerId]
|
|
104
|
+
properties:
|
|
105
|
+
customerId:
|
|
106
|
+
type: string
|
|
107
|
+
installId:
|
|
108
|
+
type: string
|
|
109
|
+
FunnelAnalyticsResponse:
|
|
110
|
+
type: object
|
|
111
|
+
properties:
|
|
112
|
+
totalEvents:
|
|
113
|
+
type: integer
|
|
114
|
+
stageCounts:
|
|
115
|
+
type: object
|
|
116
|
+
properties:
|
|
117
|
+
acquisition:
|
|
118
|
+
type: integer
|
|
119
|
+
activation:
|
|
120
|
+
type: integer
|
|
121
|
+
paid:
|
|
122
|
+
type: integer
|
|
123
|
+
eventCounts:
|
|
124
|
+
type: object
|
|
125
|
+
additionalProperties:
|
|
126
|
+
type: integer
|
|
127
|
+
conversionRates:
|
|
128
|
+
type: object
|
|
129
|
+
properties:
|
|
130
|
+
acquisitionToActivation:
|
|
131
|
+
type: number
|
|
132
|
+
activationToPaid:
|
|
133
|
+
type: number
|
|
134
|
+
acquisitionToPaid:
|
|
135
|
+
type: number
|
|
83
136
|
paths:
|
|
84
137
|
/healthz:
|
|
85
138
|
get:
|
|
@@ -113,6 +166,18 @@ paths:
|
|
|
113
166
|
description: Aggregated feedback statistics
|
|
114
167
|
'401':
|
|
115
168
|
description: Unauthorized
|
|
169
|
+
/v1/analytics/funnel:
|
|
170
|
+
get:
|
|
171
|
+
operationId: getFunnelAnalytics
|
|
172
|
+
responses:
|
|
173
|
+
'200':
|
|
174
|
+
description: Acquisition/activation/paid funnel metrics from append-only ledger
|
|
175
|
+
content:
|
|
176
|
+
application/json:
|
|
177
|
+
schema:
|
|
178
|
+
$ref: '#/components/schemas/FunnelAnalyticsResponse'
|
|
179
|
+
'401':
|
|
180
|
+
description: Unauthorized
|
|
116
181
|
/v1/intents/catalog:
|
|
117
182
|
get:
|
|
118
183
|
operationId: listIntentCatalog
|
|
@@ -290,3 +355,60 @@ paths:
|
|
|
290
355
|
description: Recent provenance events
|
|
291
356
|
'401':
|
|
292
357
|
description: Unauthorized
|
|
358
|
+
/v1/billing/checkout:
|
|
359
|
+
post:
|
|
360
|
+
operationId: createBillingCheckoutSession
|
|
361
|
+
security: []
|
|
362
|
+
requestBody:
|
|
363
|
+
required: false
|
|
364
|
+
content:
|
|
365
|
+
application/json:
|
|
366
|
+
schema:
|
|
367
|
+
$ref: '#/components/schemas/BillingCheckoutRequest'
|
|
368
|
+
responses:
|
|
369
|
+
'200':
|
|
370
|
+
description: Stripe checkout session created
|
|
371
|
+
/v1/billing/usage:
|
|
372
|
+
get:
|
|
373
|
+
operationId: getBillingUsage
|
|
374
|
+
responses:
|
|
375
|
+
'200':
|
|
376
|
+
description: Usage count for authenticated billing key
|
|
377
|
+
'401':
|
|
378
|
+
description: Unauthorized
|
|
379
|
+
/v1/billing/provision:
|
|
380
|
+
post:
|
|
381
|
+
operationId: provisionBillingKey
|
|
382
|
+
requestBody:
|
|
383
|
+
required: true
|
|
384
|
+
content:
|
|
385
|
+
application/json:
|
|
386
|
+
schema:
|
|
387
|
+
$ref: '#/components/schemas/BillingProvisionRequest'
|
|
388
|
+
responses:
|
|
389
|
+
'200':
|
|
390
|
+
description: API key provisioned
|
|
391
|
+
'400':
|
|
392
|
+
description: Missing required customerId
|
|
393
|
+
'401':
|
|
394
|
+
description: Unauthorized
|
|
395
|
+
'403':
|
|
396
|
+
description: Forbidden - requires static RLHF_API_KEY admin token
|
|
397
|
+
/v1/billing/webhook:
|
|
398
|
+
post:
|
|
399
|
+
operationId: stripeBillingWebhook
|
|
400
|
+
security: []
|
|
401
|
+
responses:
|
|
402
|
+
'200':
|
|
403
|
+
description: Webhook accepted
|
|
404
|
+
'400':
|
|
405
|
+
description: Invalid webhook signature or payload
|
|
406
|
+
/v1/billing/github-webhook:
|
|
407
|
+
post:
|
|
408
|
+
operationId: githubMarketplaceWebhook
|
|
409
|
+
security: []
|
|
410
|
+
responses:
|
|
411
|
+
'200':
|
|
412
|
+
description: Webhook accepted
|
|
413
|
+
'400':
|
|
414
|
+
description: Invalid webhook signature or payload
|
|
@@ -556,32 +556,70 @@ async function handleRequest(message) {
|
|
|
556
556
|
throw new Error(`Unsupported method: ${message.method}`);
|
|
557
557
|
}
|
|
558
558
|
|
|
559
|
-
function writeMessage(payload) {
|
|
559
|
+
function writeMessage(payload, transport = 'framed') {
|
|
560
560
|
const json = JSON.stringify(payload);
|
|
561
|
+
if (transport === 'ndjson') {
|
|
562
|
+
process.stdout.write(`${json}\n`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
561
565
|
process.stdout.write(`Content-Length: ${Buffer.byteLength(json, 'utf8')}\r\n\r\n${json}`);
|
|
562
566
|
}
|
|
563
567
|
|
|
568
|
+
function parseWithTransport(raw, transport) {
|
|
569
|
+
try {
|
|
570
|
+
return JSON.parse(raw);
|
|
571
|
+
} catch (err) {
|
|
572
|
+
err.transport = transport;
|
|
573
|
+
throw err;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
564
577
|
let buffer = Buffer.alloc(0);
|
|
578
|
+
let stdioStarted = false;
|
|
579
|
+
|
|
580
|
+
function hasContentLengthPrefix() {
|
|
581
|
+
if (buffer.length === 0) return false;
|
|
582
|
+
const probe = buffer.slice(0, Math.min(buffer.length, 32)).toString('utf8').toLowerCase();
|
|
583
|
+
return 'content-length:'.startsWith(probe) || probe.startsWith('content-length:');
|
|
584
|
+
}
|
|
565
585
|
|
|
566
586
|
function tryReadMessage() {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
587
|
+
const headerEndCrLf = buffer.indexOf('\r\n\r\n');
|
|
588
|
+
const headerEndLf = buffer.indexOf('\n\n');
|
|
589
|
+
const hasFramedHeader = headerEndCrLf !== -1 || headerEndLf !== -1;
|
|
590
|
+
|
|
591
|
+
if (hasFramedHeader) {
|
|
592
|
+
const useCrLf = headerEndCrLf !== -1 && (headerEndLf === -1 || headerEndCrLf < headerEndLf);
|
|
593
|
+
const headerEnd = useCrLf ? headerEndCrLf : headerEndLf;
|
|
594
|
+
const separatorLength = useCrLf ? 4 : 2;
|
|
595
|
+
const headerRaw = buffer.slice(0, headerEnd).toString('utf8');
|
|
596
|
+
const match = headerRaw.match(/Content-Length:\s*(\d+)/i);
|
|
597
|
+
if (!match) {
|
|
598
|
+
buffer = buffer.slice(headerEnd + separatorLength);
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const length = Number(match[1]);
|
|
603
|
+
const totalSize = headerEnd + separatorLength + length;
|
|
604
|
+
if (buffer.length < totalSize) return null;
|
|
605
|
+
|
|
606
|
+
const body = buffer.slice(headerEnd + separatorLength, totalSize).toString('utf8');
|
|
607
|
+
buffer = buffer.slice(totalSize);
|
|
608
|
+
|
|
609
|
+
return { message: parseWithTransport(body, 'framed'), transport: 'framed' };
|
|
575
610
|
}
|
|
576
611
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
612
|
+
// Codex MCP client currently sends newline-delimited JSON during startup.
|
|
613
|
+
if (hasContentLengthPrefix()) return null;
|
|
614
|
+
|
|
615
|
+
const newlineIndex = buffer.indexOf('\n');
|
|
616
|
+
if (newlineIndex === -1) return null;
|
|
580
617
|
|
|
581
|
-
const
|
|
582
|
-
buffer = buffer.slice(
|
|
618
|
+
const line = buffer.slice(0, newlineIndex).toString('utf8').trim();
|
|
619
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
620
|
+
if (!line) return null;
|
|
583
621
|
|
|
584
|
-
return
|
|
622
|
+
return { message: parseWithTransport(line, 'ndjson'), transport: 'ndjson' };
|
|
585
623
|
}
|
|
586
624
|
|
|
587
625
|
async function onData(chunk) {
|
|
@@ -590,39 +628,60 @@ async function onData(chunk) {
|
|
|
590
628
|
while (true) {
|
|
591
629
|
const message = tryReadMessage();
|
|
592
630
|
if (!message) return;
|
|
631
|
+
const envelope = message;
|
|
632
|
+
const request = envelope.message;
|
|
633
|
+
const transport = envelope.transport;
|
|
593
634
|
|
|
594
|
-
if (!Object.prototype.hasOwnProperty.call(
|
|
635
|
+
if (!Object.prototype.hasOwnProperty.call(request, 'id')) {
|
|
595
636
|
continue;
|
|
596
637
|
}
|
|
597
638
|
|
|
598
639
|
try {
|
|
599
|
-
const result = await handleRequest(
|
|
600
|
-
writeMessage({ jsonrpc: '2.0', id:
|
|
640
|
+
const result = await handleRequest(request);
|
|
641
|
+
writeMessage({ jsonrpc: '2.0', id: request.id, result }, transport);
|
|
601
642
|
} catch (err) {
|
|
602
643
|
writeMessage({
|
|
603
644
|
jsonrpc: '2.0',
|
|
604
|
-
id:
|
|
645
|
+
id: request.id,
|
|
605
646
|
error: {
|
|
606
647
|
code: -32603,
|
|
607
648
|
message: err.message || 'Internal error',
|
|
608
649
|
},
|
|
609
|
-
});
|
|
650
|
+
}, transport);
|
|
610
651
|
}
|
|
611
652
|
}
|
|
612
653
|
}
|
|
613
654
|
|
|
655
|
+
function startStdioServer() {
|
|
656
|
+
if (stdioStarted) return;
|
|
657
|
+
stdioStarted = true;
|
|
658
|
+
|
|
659
|
+
// Keep the process alive even if stdin closes (prevents premature exit
|
|
660
|
+
// when launched by MCP clients like Claude Code, Codex, Gemini CLI).
|
|
661
|
+
const keepAlive = setInterval(() => {}, 60_000);
|
|
662
|
+
|
|
663
|
+
process.stdin.resume();
|
|
664
|
+
process.stdin.on('data', (chunk) => {
|
|
665
|
+
onData(chunk).catch((err) => {
|
|
666
|
+
const transport = err && err.transport === 'ndjson' ? 'ndjson' : 'framed';
|
|
667
|
+
writeMessage({ jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message } }, transport);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
process.stdin.on('end', () => {
|
|
671
|
+
// stdin closed — clean up and exit gracefully
|
|
672
|
+
clearInterval(keepAlive);
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
|
|
614
676
|
module.exports = {
|
|
615
677
|
TOOLS,
|
|
616
678
|
handleRequest,
|
|
617
679
|
callTool,
|
|
618
680
|
resolveSafePath,
|
|
619
681
|
SAFE_DATA_DIR,
|
|
682
|
+
startStdioServer,
|
|
620
683
|
};
|
|
621
684
|
|
|
622
685
|
if (require.main === module) {
|
|
623
|
-
|
|
624
|
-
onData(chunk).catch((err) => {
|
|
625
|
-
writeMessage({ jsonrpc: '2.0', id: null, error: { code: -32603, message: err.message } });
|
|
626
|
-
});
|
|
627
|
-
});
|
|
686
|
+
startStdioServer();
|
|
628
687
|
}
|
package/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
|
+
const crypto = require('crypto');
|
|
17
18
|
const { execSync } = require('child_process');
|
|
18
19
|
|
|
19
20
|
const COMMAND = process.argv[2];
|
|
@@ -124,6 +125,7 @@ function setupCursor() {
|
|
|
124
125
|
|
|
125
126
|
function init() {
|
|
126
127
|
const rlhfDir = path.join(CWD, '.rlhf');
|
|
128
|
+
const configPath = path.join(rlhfDir, 'config.json');
|
|
127
129
|
|
|
128
130
|
if (!fs.existsSync(rlhfDir)) {
|
|
129
131
|
fs.mkdirSync(rlhfDir, { recursive: true });
|
|
@@ -132,15 +134,28 @@ function init() {
|
|
|
132
134
|
console.log('.rlhf/ already exists — updating config');
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
let existingInstallId = null;
|
|
138
|
+
if (fs.existsSync(configPath)) {
|
|
139
|
+
try {
|
|
140
|
+
const existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
141
|
+
if (existingConfig && typeof existingConfig.installId === 'string' && existingConfig.installId.trim()) {
|
|
142
|
+
existingInstallId = existingConfig.installId.trim();
|
|
143
|
+
}
|
|
144
|
+
} catch (_) {
|
|
145
|
+
// Ignore invalid existing config and write a fresh one below.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
135
149
|
const config = {
|
|
136
150
|
version: pkgVersion(),
|
|
137
151
|
apiUrl: process.env.RLHF_API_URL || 'http://localhost:3000',
|
|
138
152
|
logPath: '.rlhf/feedback-log.jsonl',
|
|
139
153
|
memoryPath: '.rlhf/memory-log.jsonl',
|
|
154
|
+
installId: existingInstallId || crypto.randomUUID(),
|
|
140
155
|
createdAt: new Date().toISOString(),
|
|
141
156
|
};
|
|
142
157
|
|
|
143
|
-
fs.writeFileSync(
|
|
158
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
144
159
|
console.log('Wrote .rlhf/config.json');
|
|
145
160
|
|
|
146
161
|
// Always create .mcp.json (project-level MCP config used by Claude, Codex, Cursor)
|
|
@@ -189,6 +204,22 @@ function init() {
|
|
|
189
204
|
console.log('');
|
|
190
205
|
console.log(`rlhf-feedback-loop v${pkgVersion()} initialized.`);
|
|
191
206
|
console.log('Run: npx rlhf-feedback-loop help');
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const { appendFunnelEvent } = require(path.join(PKG_ROOT, 'scripts', 'billing'));
|
|
210
|
+
appendFunnelEvent({
|
|
211
|
+
stage: 'acquisition',
|
|
212
|
+
event: 'cli_init_completed',
|
|
213
|
+
evidence: 'cli_init_completed',
|
|
214
|
+
installId: config.installId,
|
|
215
|
+
metadata: {
|
|
216
|
+
cwd: CWD,
|
|
217
|
+
version: config.version,
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
} catch (_) {
|
|
221
|
+
// Avoid failing init if telemetry write cannot be performed.
|
|
222
|
+
}
|
|
192
223
|
}
|
|
193
224
|
|
|
194
225
|
function capture() {
|
|
@@ -351,7 +382,8 @@ function serve() {
|
|
|
351
382
|
|
|
352
383
|
// Start MCP server over stdio
|
|
353
384
|
const mcpServer = path.join(PKG_ROOT, 'adapters', 'mcp', 'server-stdio.js');
|
|
354
|
-
require(mcpServer);
|
|
385
|
+
const { startStdioServer } = require(mcpServer);
|
|
386
|
+
startStdioServer();
|
|
355
387
|
}
|
|
356
388
|
|
|
357
389
|
function install() {
|
|
@@ -442,7 +474,6 @@ switch (COMMAND) {
|
|
|
442
474
|
prove();
|
|
443
475
|
break;
|
|
444
476
|
case 'start-api':
|
|
445
|
-
case 'serve':
|
|
446
477
|
startApi();
|
|
447
478
|
break;
|
|
448
479
|
case 'help':
|
package/openapi/openapi.yaml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: RLHF Feedback Loop API
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
description: |
|
|
6
6
|
Production API for feedback capture, schema-validated memory promotion,
|
|
7
7
|
prevention rule generation, and DPO export.
|
|
8
8
|
servers:
|
|
9
|
-
- url:
|
|
9
|
+
- url: https://rlhf-feedback-loop-710216278770.us-central1.run.app
|
|
10
10
|
security:
|
|
11
11
|
- bearerAuth: []
|
|
12
12
|
components:
|
|
@@ -80,6 +80,59 @@ components:
|
|
|
80
80
|
type: string
|
|
81
81
|
approved:
|
|
82
82
|
type: boolean
|
|
83
|
+
BillingCheckoutRequest:
|
|
84
|
+
type: object
|
|
85
|
+
properties:
|
|
86
|
+
successUrl:
|
|
87
|
+
type: string
|
|
88
|
+
format: uri
|
|
89
|
+
cancelUrl:
|
|
90
|
+
type: string
|
|
91
|
+
format: uri
|
|
92
|
+
customerEmail:
|
|
93
|
+
type: string
|
|
94
|
+
format: email
|
|
95
|
+
installId:
|
|
96
|
+
type: string
|
|
97
|
+
metadata:
|
|
98
|
+
type: object
|
|
99
|
+
additionalProperties:
|
|
100
|
+
type: string
|
|
101
|
+
BillingProvisionRequest:
|
|
102
|
+
type: object
|
|
103
|
+
required: [customerId]
|
|
104
|
+
properties:
|
|
105
|
+
customerId:
|
|
106
|
+
type: string
|
|
107
|
+
installId:
|
|
108
|
+
type: string
|
|
109
|
+
FunnelAnalyticsResponse:
|
|
110
|
+
type: object
|
|
111
|
+
properties:
|
|
112
|
+
totalEvents:
|
|
113
|
+
type: integer
|
|
114
|
+
stageCounts:
|
|
115
|
+
type: object
|
|
116
|
+
properties:
|
|
117
|
+
acquisition:
|
|
118
|
+
type: integer
|
|
119
|
+
activation:
|
|
120
|
+
type: integer
|
|
121
|
+
paid:
|
|
122
|
+
type: integer
|
|
123
|
+
eventCounts:
|
|
124
|
+
type: object
|
|
125
|
+
additionalProperties:
|
|
126
|
+
type: integer
|
|
127
|
+
conversionRates:
|
|
128
|
+
type: object
|
|
129
|
+
properties:
|
|
130
|
+
acquisitionToActivation:
|
|
131
|
+
type: number
|
|
132
|
+
activationToPaid:
|
|
133
|
+
type: number
|
|
134
|
+
acquisitionToPaid:
|
|
135
|
+
type: number
|
|
83
136
|
paths:
|
|
84
137
|
/healthz:
|
|
85
138
|
get:
|
|
@@ -113,6 +166,18 @@ paths:
|
|
|
113
166
|
description: Aggregated feedback statistics
|
|
114
167
|
'401':
|
|
115
168
|
description: Unauthorized
|
|
169
|
+
/v1/analytics/funnel:
|
|
170
|
+
get:
|
|
171
|
+
operationId: getFunnelAnalytics
|
|
172
|
+
responses:
|
|
173
|
+
'200':
|
|
174
|
+
description: Acquisition/activation/paid funnel metrics from append-only ledger
|
|
175
|
+
content:
|
|
176
|
+
application/json:
|
|
177
|
+
schema:
|
|
178
|
+
$ref: '#/components/schemas/FunnelAnalyticsResponse'
|
|
179
|
+
'401':
|
|
180
|
+
description: Unauthorized
|
|
116
181
|
/v1/intents/catalog:
|
|
117
182
|
get:
|
|
118
183
|
operationId: listIntentCatalog
|
|
@@ -290,3 +355,60 @@ paths:
|
|
|
290
355
|
description: Recent provenance events
|
|
291
356
|
'401':
|
|
292
357
|
description: Unauthorized
|
|
358
|
+
/v1/billing/checkout:
|
|
359
|
+
post:
|
|
360
|
+
operationId: createBillingCheckoutSession
|
|
361
|
+
security: []
|
|
362
|
+
requestBody:
|
|
363
|
+
required: false
|
|
364
|
+
content:
|
|
365
|
+
application/json:
|
|
366
|
+
schema:
|
|
367
|
+
$ref: '#/components/schemas/BillingCheckoutRequest'
|
|
368
|
+
responses:
|
|
369
|
+
'200':
|
|
370
|
+
description: Stripe checkout session created
|
|
371
|
+
/v1/billing/usage:
|
|
372
|
+
get:
|
|
373
|
+
operationId: getBillingUsage
|
|
374
|
+
responses:
|
|
375
|
+
'200':
|
|
376
|
+
description: Usage count for authenticated billing key
|
|
377
|
+
'401':
|
|
378
|
+
description: Unauthorized
|
|
379
|
+
/v1/billing/provision:
|
|
380
|
+
post:
|
|
381
|
+
operationId: provisionBillingKey
|
|
382
|
+
requestBody:
|
|
383
|
+
required: true
|
|
384
|
+
content:
|
|
385
|
+
application/json:
|
|
386
|
+
schema:
|
|
387
|
+
$ref: '#/components/schemas/BillingProvisionRequest'
|
|
388
|
+
responses:
|
|
389
|
+
'200':
|
|
390
|
+
description: API key provisioned
|
|
391
|
+
'400':
|
|
392
|
+
description: Missing required customerId
|
|
393
|
+
'401':
|
|
394
|
+
description: Unauthorized
|
|
395
|
+
'403':
|
|
396
|
+
description: Forbidden - requires static RLHF_API_KEY admin token
|
|
397
|
+
/v1/billing/webhook:
|
|
398
|
+
post:
|
|
399
|
+
operationId: stripeBillingWebhook
|
|
400
|
+
security: []
|
|
401
|
+
responses:
|
|
402
|
+
'200':
|
|
403
|
+
description: Webhook accepted
|
|
404
|
+
'400':
|
|
405
|
+
description: Invalid webhook signature or payload
|
|
406
|
+
/v1/billing/github-webhook:
|
|
407
|
+
post:
|
|
408
|
+
operationId: githubMarketplaceWebhook
|
|
409
|
+
security: []
|
|
410
|
+
responses:
|
|
411
|
+
'200':
|
|
412
|
+
description: Webhook accepted
|
|
413
|
+
'400':
|
|
414
|
+
description: Invalid webhook signature or payload
|