@friehub/blueprint 0.1.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/.github/workflows/ci.yml +122 -0
- package/.github/workflows/publish.yml +24 -0
- package/README.md +266 -0
- package/adapters/analytics/amplitude.yaml +44 -0
- package/adapters/analytics/mixpanel.yaml +47 -0
- package/adapters/analytics/segment.yaml +40 -0
- package/adapters/auth/auth0.yaml +56 -0
- package/adapters/auth/clerk.yaml +53 -0
- package/adapters/auth/supertokens.yaml +55 -0
- package/adapters/billing/paddle.yaml +57 -0
- package/adapters/billing/stripe.yaml +49 -0
- package/adapters/caching/memcached.yaml +28 -0
- package/adapters/caching/redis.yaml +37 -0
- package/adapters/chargebacks/chargebacks911.yaml +45 -0
- package/adapters/chargebacks/stripe.yaml +45 -0
- package/adapters/crm_leads/hubspot.yaml +43 -0
- package/adapters/crm_leads/salesforce.yaml +60 -0
- package/adapters/customer_support/intercom.yaml +44 -0
- package/adapters/customer_support/zendesk.yaml +51 -0
- package/adapters/donations/paypal.yaml +47 -0
- package/adapters/donations/stripe.yaml +47 -0
- package/adapters/emails/mailgun.yaml +47 -0
- package/adapters/emails/resend.yaml +43 -0
- package/adapters/emails/sendgrid.yaml +43 -0
- package/adapters/error_tracking/bugsnag.yaml +52 -0
- package/adapters/error_tracking/sentry.yaml +58 -0
- package/adapters/feature_flags/flagsmith.yaml +41 -0
- package/adapters/feature_flags/launchdarkly.yaml +41 -0
- package/adapters/feature_flags/unleash.yaml +41 -0
- package/adapters/fraud_detection/riskified.yaml +41 -0
- package/adapters/fraud_detection/sift.yaml +40 -0
- package/adapters/fulfillment/easyship.yaml +51 -0
- package/adapters/fulfillment/shipengine.yaml +51 -0
- package/adapters/incident_management/opsgenie.yaml +49 -0
- package/adapters/incident_management/pagerduty.yaml +48 -0
- package/adapters/invoicing/freshbooks.yaml +54 -0
- package/adapters/invoicing/stripe.yaml +47 -0
- package/adapters/ip_intelligence/ipinfo.yaml +37 -0
- package/adapters/ip_intelligence/maxmind.yaml +39 -0
- package/adapters/jobs/bullmq.yaml +54 -0
- package/adapters/jobs/temporal.yaml +53 -0
- package/adapters/kyc/jumio.yaml +54 -0
- package/adapters/kyc/onfido.yaml +53 -0
- package/adapters/media/cloudinary.yaml +48 -0
- package/adapters/media/imgix.yaml +47 -0
- package/adapters/notifications/firebase.yaml +45 -0
- package/adapters/notifications/novu.yaml +46 -0
- package/adapters/notifications/onesignal.yaml +45 -0
- package/adapters/payments/adyen.yaml +46 -0
- package/adapters/payments/paystack.yaml +45 -0
- package/adapters/payments/stripe.yaml +49 -0
- package/adapters/payouts/paypal.yaml +49 -0
- package/adapters/payouts/stripe.yaml +49 -0
- package/adapters/projects/asana.yaml +49 -0
- package/adapters/projects/jira.yaml +58 -0
- package/adapters/projects/linear.yaml +49 -0
- package/adapters/queues/bullmq.yaml +47 -0
- package/adapters/queues/rabbitmq.yaml +51 -0
- package/adapters/queues/sqs.yaml +45 -0
- package/adapters/rate_limiting/cloudflare.yaml +37 -0
- package/adapters/rate_limiting/upstash.yaml +35 -0
- package/adapters/search/algolia.yaml +39 -0
- package/adapters/search/meilisearch.yaml +39 -0
- package/adapters/search/typesense.yaml +42 -0
- package/adapters/shipping/easyship.yaml +45 -0
- package/adapters/shipping/shipengine.yaml +45 -0
- package/adapters/sms/twilio.yaml +41 -0
- package/adapters/sms/vonage.yaml +41 -0
- package/adapters/storage/azure-blob.yaml +42 -0
- package/adapters/storage/gcs.yaml +41 -0
- package/adapters/storage/s3.yaml +49 -0
- package/adapters/subscriptions/chargebee.yaml +32 -0
- package/adapters/subscriptions/stripe.yaml +37 -0
- package/adapters/tasks/asana.yaml +50 -0
- package/adapters/tasks/jira.yaml +59 -0
- package/adapters/tasks/linear.yaml +50 -0
- package/adapters/taxation/avalara.yaml +52 -0
- package/adapters/taxation/taxjar.yaml +48 -0
- package/adapters/trace_query/datadog.yaml +49 -0
- package/adapters/trace_query/honeycomb.yaml +42 -0
- package/adapters/trace_query/jaeger.yaml +42 -0
- package/adapters/web_analytics/google-analytics.yaml +42 -0
- package/adapters/web_analytics/plausible.yaml +34 -0
- package/adapters/web_analytics/posthog.yaml +34 -0
- package/adapters/webhooks/relay.yaml +35 -0
- package/adapters/webhooks/svix.yaml +41 -0
- package/blueprint.json +5 -0
- package/blueprinter_system_design.svg +139 -0
- package/catalog.json +37943 -0
- package/dist/cli/commands.js +362 -0
- package/dist/cli/help.js +211 -0
- package/dist/cli/render.js +109 -0
- package/dist/cli.js +69 -0
- package/dist/core/adapters/adapter-audit.test.js +85 -0
- package/dist/core/adapters/adapter.test.js +66 -0
- package/dist/core/adapters/index.js +4 -0
- package/dist/core/adapters/load.js +131 -0
- package/dist/core/adapters/resolve.js +78 -0
- package/dist/core/adapters/select.js +80 -0
- package/dist/core/adapters/types.js +1 -0
- package/dist/core/adapters/validate.js +121 -0
- package/dist/core/catalog.js +3 -0
- package/dist/core/collectors.js +126 -0
- package/dist/core/discovery.js +57 -0
- package/dist/core/edge-cases.test.js +147 -0
- package/dist/core/envelope.js +1 -0
- package/dist/core/graph.js +123 -0
- package/dist/core/graph.test.js +62 -0
- package/dist/core/implement.js +48 -0
- package/dist/core/index.js +9 -0
- package/dist/core/load-catalog.js +24 -0
- package/dist/core/parse-document.js +114 -0
- package/dist/core/parse-document.test.js +104 -0
- package/dist/core/parser.js +6 -0
- package/dist/core/parser.test.js +134 -0
- package/dist/core/resolve.js +119 -0
- package/dist/core/resolve.test.js +108 -0
- package/dist/core/scanner.js +163 -0
- package/dist/core/search.js +34 -0
- package/dist/core/search.test.js +43 -0
- package/dist/core/section-body.js +258 -0
- package/dist/core/sections.js +53 -0
- package/dist/core/verify-implement.test.js +123 -0
- package/dist/core/verify.js +156 -0
- package/dist/generators/engine.js +86 -0
- package/dist/generators/generator.test.js +112 -0
- package/dist/generators/index.js +3 -0
- package/dist/generators/prototype/index.js +242 -0
- package/dist/generators/render.js +146 -0
- package/dist/generators/types.js +124 -0
- package/dist/generators/typescript/helpers.js +125 -0
- package/dist/generators/typescript/index.js +206 -0
- package/dist/index.js +8 -0
- package/dist/mcp/server.js +202 -0
- package/dist/mcp/server.test.js +136 -0
- package/dist/utils/args.js +142 -0
- package/package.json +38 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { loadCatalogFromRoot } from "../core/load-catalog.js";
|
|
6
|
+
import { resolve as resolveDeps, detectCycles } from "../core/resolve.js";
|
|
7
|
+
import { searchModules } from "../core/search.js";
|
|
8
|
+
import { loadAdapters } from "../core/adapters/load.js";
|
|
9
|
+
const ROOT_DIR = process.env.BLUEPRINT_ROOT || process.cwd();
|
|
10
|
+
const server = new Server({ name: "engineering-blueprint", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
11
|
+
let catalog = null;
|
|
12
|
+
let adapters = [];
|
|
13
|
+
async function loadData() {
|
|
14
|
+
if (!catalog) {
|
|
15
|
+
const result = await loadCatalogFromRoot(ROOT_DIR, "loose");
|
|
16
|
+
catalog = result.value;
|
|
17
|
+
const adapterResult = await loadAdapters(`${ROOT_DIR}/adapters`);
|
|
18
|
+
adapters = adapterResult.adapters;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function moduleSummary(m) {
|
|
22
|
+
return {
|
|
23
|
+
name: m.name,
|
|
24
|
+
version: m.version,
|
|
25
|
+
functionCount: m.functions.length,
|
|
26
|
+
hardDeps: m.hardDeps,
|
|
27
|
+
softDeps: m.softDeps,
|
|
28
|
+
coreInherits: m.coreInherits,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
32
|
+
tools: [
|
|
33
|
+
{
|
|
34
|
+
name: "list_modules",
|
|
35
|
+
description: "List all available module contracts (108 modules)",
|
|
36
|
+
inputSchema: { type: "object", properties: {} },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "get_module",
|
|
40
|
+
description: "Get the full contract for a module including functions, types, and dependencies",
|
|
41
|
+
inputSchema: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: { name: { type: "string", description: "Module name (e.g., payments, billing, auth)" } },
|
|
44
|
+
required: ["name"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "search_modules",
|
|
49
|
+
description: "Search modules by name, summary, or function name",
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: "object",
|
|
52
|
+
properties: { query: { type: "string", description: "Search query" } },
|
|
53
|
+
required: ["query"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "resolve_deps",
|
|
58
|
+
description: "Resolve a set of modules with all transitive dependencies",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
modules: { type: "array", items: { type: "string" }, description: "Module names to resolve" },
|
|
63
|
+
},
|
|
64
|
+
required: ["modules"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "list_adapters",
|
|
69
|
+
description: "List available adapters (83 adapters across 35 modules)",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
module: { type: "string", description: "Optional: filter by module name" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "get_adapter",
|
|
79
|
+
description: "Get adapter details including config requirements",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
module: { type: "string", description: "Module name" },
|
|
84
|
+
provider: { type: "string", description: "Adapter provider name (e.g., stripe, redis)" },
|
|
85
|
+
},
|
|
86
|
+
required: ["module", "provider"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "get_dependency_graph",
|
|
91
|
+
description: "Get dependency graph (hard deps, soft deps) for a module",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: { module: { type: "string", description: "Module name" } },
|
|
95
|
+
required: ["module"],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
}));
|
|
100
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
101
|
+
await loadData();
|
|
102
|
+
const { name, arguments: args } = request.params;
|
|
103
|
+
const input = (args || {});
|
|
104
|
+
try {
|
|
105
|
+
switch (name) {
|
|
106
|
+
case "list_modules": {
|
|
107
|
+
const modules = (catalog?.modules || []).map(moduleSummary);
|
|
108
|
+
return { content: [{ type: "text", text: JSON.stringify({ total: modules.length, modules }, null, 2) }] };
|
|
109
|
+
}
|
|
110
|
+
case "get_module": {
|
|
111
|
+
const modName = input.name;
|
|
112
|
+
const mod = catalog?.modules.find((m) => m.name === modName);
|
|
113
|
+
if (!mod)
|
|
114
|
+
return { content: [{ type: "text", text: `Module "${modName}" not found` }] };
|
|
115
|
+
return { content: [{ type: "text", text: JSON.stringify(mod, null, 2) }] };
|
|
116
|
+
}
|
|
117
|
+
case "search_modules": {
|
|
118
|
+
const query = input.query;
|
|
119
|
+
const results = searchModules(catalog, query);
|
|
120
|
+
return {
|
|
121
|
+
content: [{
|
|
122
|
+
type: "text",
|
|
123
|
+
text: JSON.stringify({
|
|
124
|
+
query,
|
|
125
|
+
total: results.length,
|
|
126
|
+
results: results.slice(0, 10).map((r) => ({
|
|
127
|
+
name: r.module.name,
|
|
128
|
+
score: r.score,
|
|
129
|
+
matchType: r.matchType,
|
|
130
|
+
summary: r.module.summary,
|
|
131
|
+
})),
|
|
132
|
+
}, null, 2),
|
|
133
|
+
}],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
case "resolve_deps": {
|
|
137
|
+
const modules = input.modules || [];
|
|
138
|
+
const cycles = detectCycles(catalog);
|
|
139
|
+
if (cycles.length > 0) {
|
|
140
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: "Cycles detected", cycles }) }] };
|
|
141
|
+
}
|
|
142
|
+
const resolved = resolveDeps(catalog, modules);
|
|
143
|
+
return { content: [{ type: "text", text: JSON.stringify(resolved, null, 2) }] };
|
|
144
|
+
}
|
|
145
|
+
case "list_adapters": {
|
|
146
|
+
const modName = input.module;
|
|
147
|
+
const filtered = modName ? adapters.filter((a) => a.module === modName) : adapters;
|
|
148
|
+
const grouped = {};
|
|
149
|
+
for (const a of filtered) {
|
|
150
|
+
const mod = a.module;
|
|
151
|
+
if (!grouped[mod])
|
|
152
|
+
grouped[mod] = [];
|
|
153
|
+
grouped[mod].push(a.name);
|
|
154
|
+
}
|
|
155
|
+
return { content: [{ type: "text", text: JSON.stringify(grouped, null, 2) }] };
|
|
156
|
+
}
|
|
157
|
+
case "get_adapter": {
|
|
158
|
+
const modName = input.module;
|
|
159
|
+
const provider = input.provider;
|
|
160
|
+
const adapter = adapters.find((a) => a.module === modName && a.name === provider);
|
|
161
|
+
if (!adapter)
|
|
162
|
+
return { content: [{ type: "text", text: `Adapter "${provider}" not found for module "${modName}"` }] };
|
|
163
|
+
return { content: [{ type: "text", text: JSON.stringify(adapter, null, 2) }] };
|
|
164
|
+
}
|
|
165
|
+
case "get_dependency_graph": {
|
|
166
|
+
const modName = input.module;
|
|
167
|
+
const mod = catalog?.modules.find((m) => m.name === modName);
|
|
168
|
+
if (!mod)
|
|
169
|
+
return { content: [{ type: "text", text: `Module "${modName}" not found` }] };
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: JSON.stringify({
|
|
174
|
+
module: modName,
|
|
175
|
+
hardDeps: mod.hardDeps,
|
|
176
|
+
softDeps: mod.softDeps,
|
|
177
|
+
coreInherits: mod.coreInherits,
|
|
178
|
+
recommends: mod.softDeps,
|
|
179
|
+
requiredBy: catalog?.modules.filter((m) => m.hardDeps.includes(modName)).map((m) => m.name),
|
|
180
|
+
softDepsOf: catalog?.modules.filter((m) => m.softDeps.includes(modName)).map((m) => m.name),
|
|
181
|
+
}, null, 2),
|
|
182
|
+
}],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
async function startMCP() {
|
|
194
|
+
const transport = new StdioServerTransport();
|
|
195
|
+
await server.connect(transport);
|
|
196
|
+
console.error("Engineering Blueprinter MCP server running on stdio");
|
|
197
|
+
console.error(`Root directory: ${ROOT_DIR}`);
|
|
198
|
+
}
|
|
199
|
+
startMCP().catch((err) => {
|
|
200
|
+
console.error("Failed to start MCP server:", err);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { strict as assert } from "node:assert";
|
|
2
|
+
import { describe, it, before, after } from "node:test";
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
const ROOT = fileURLToPath(new URL("../../", import.meta.url));
|
|
7
|
+
const SERVER_PATH = join(ROOT, "dist", "mcp", "server.js");
|
|
8
|
+
function sendRequest(proc, request) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
const timeout = setTimeout(() => {
|
|
12
|
+
proc.kill();
|
|
13
|
+
reject(new Error("Timeout waiting for response"));
|
|
14
|
+
}, 15000);
|
|
15
|
+
proc.stdout.on("data", (chunk) => {
|
|
16
|
+
chunks.push(chunk);
|
|
17
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
18
|
+
if (text.includes("}")) {
|
|
19
|
+
clearTimeout(timeout);
|
|
20
|
+
resolve(text);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
proc.stderr.on("data", () => { });
|
|
24
|
+
proc.stdin.write(JSON.stringify(request) + "\n");
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
describe("MCP server", () => {
|
|
28
|
+
let server;
|
|
29
|
+
before(() => {
|
|
30
|
+
server = spawn("node", [SERVER_PATH], {
|
|
31
|
+
env: { ...process.env, BLUEPRINT_ROOT: ROOT },
|
|
32
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
after(() => {
|
|
36
|
+
if (server && !server.killed)
|
|
37
|
+
server.kill();
|
|
38
|
+
});
|
|
39
|
+
it("responds to tools/list", { timeout: 15000 }, async () => {
|
|
40
|
+
const response = await sendRequest(server, {
|
|
41
|
+
jsonrpc: "2.0",
|
|
42
|
+
id: 1,
|
|
43
|
+
method: "tools/list",
|
|
44
|
+
params: {},
|
|
45
|
+
});
|
|
46
|
+
const json = JSON.parse(response);
|
|
47
|
+
assert.ok(json.result, "should have result");
|
|
48
|
+
assert.ok(json.result.tools, "should have tools array");
|
|
49
|
+
assert.ok(json.result.tools.length >= 7, "should have at least 7 tools");
|
|
50
|
+
});
|
|
51
|
+
it("responds to list_modules", { timeout: 15000 }, async () => {
|
|
52
|
+
const response = await sendRequest(server, {
|
|
53
|
+
jsonrpc: "2.0",
|
|
54
|
+
id: 2,
|
|
55
|
+
method: "tools/call",
|
|
56
|
+
params: { name: "list_modules", arguments: {} },
|
|
57
|
+
});
|
|
58
|
+
const json = JSON.parse(response);
|
|
59
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
60
|
+
assert.ok(content.total >= 100, "should have 108 modules");
|
|
61
|
+
assert.ok(content.modules.length >= 100);
|
|
62
|
+
});
|
|
63
|
+
it("responds to get_module for payments", { timeout: 15000 }, async () => {
|
|
64
|
+
const response = await sendRequest(server, {
|
|
65
|
+
jsonrpc: "2.0",
|
|
66
|
+
id: 3,
|
|
67
|
+
method: "tools/call",
|
|
68
|
+
params: { name: "get_module", arguments: { name: "payments" } },
|
|
69
|
+
});
|
|
70
|
+
const json = JSON.parse(response);
|
|
71
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
72
|
+
assert.equal(content.name, "payments");
|
|
73
|
+
assert.ok(content.functions.length >= 10);
|
|
74
|
+
});
|
|
75
|
+
it("returns error for unknown module", { timeout: 15000 }, async () => {
|
|
76
|
+
const response = await sendRequest(server, {
|
|
77
|
+
jsonrpc: "2.0",
|
|
78
|
+
id: 4,
|
|
79
|
+
method: "tools/call",
|
|
80
|
+
params: { name: "get_module", arguments: { name: "nonexistent" } },
|
|
81
|
+
});
|
|
82
|
+
const json = JSON.parse(response);
|
|
83
|
+
const text = json.result.content[0].text;
|
|
84
|
+
assert.ok(text.includes("not found") || text.includes("Not found"));
|
|
85
|
+
});
|
|
86
|
+
it("responds to search_modules", { timeout: 15000 }, async () => {
|
|
87
|
+
const response = await sendRequest(server, {
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
id: 5,
|
|
90
|
+
method: "tools/call",
|
|
91
|
+
params: { name: "search_modules", arguments: { query: "payment" } },
|
|
92
|
+
});
|
|
93
|
+
const json = JSON.parse(response);
|
|
94
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
95
|
+
assert.ok(content.total > 0);
|
|
96
|
+
assert.ok(content.results.some((r) => r.name === "payments"));
|
|
97
|
+
});
|
|
98
|
+
it("responds to resolve_deps", { timeout: 15000 }, async () => {
|
|
99
|
+
const response = await sendRequest(server, {
|
|
100
|
+
jsonrpc: "2.0",
|
|
101
|
+
id: 6,
|
|
102
|
+
method: "tools/call",
|
|
103
|
+
params: { name: "resolve_deps", arguments: { modules: ["billing"] } },
|
|
104
|
+
});
|
|
105
|
+
const json = JSON.parse(response);
|
|
106
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
107
|
+
assert.ok(content.modules.length > 0);
|
|
108
|
+
assert.ok(content.modules.some((m) => m.name === "billing"));
|
|
109
|
+
assert.ok(content.modules.some((m) => m.name === "payments"));
|
|
110
|
+
});
|
|
111
|
+
it("responds to list_adapters", { timeout: 15000 }, async () => {
|
|
112
|
+
const response = await sendRequest(server, {
|
|
113
|
+
jsonrpc: "2.0",
|
|
114
|
+
id: 7,
|
|
115
|
+
method: "tools/call",
|
|
116
|
+
params: { name: "list_adapters", arguments: { module: "payments" } },
|
|
117
|
+
});
|
|
118
|
+
const json = JSON.parse(response);
|
|
119
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
120
|
+
assert.ok(content.payments, "should have payments adapters");
|
|
121
|
+
assert.ok(content.payments.includes("stripe"));
|
|
122
|
+
});
|
|
123
|
+
it("responds to get_adapter", { timeout: 15000 }, async () => {
|
|
124
|
+
const response = await sendRequest(server, {
|
|
125
|
+
jsonrpc: "2.0",
|
|
126
|
+
id: 8,
|
|
127
|
+
method: "tools/call",
|
|
128
|
+
params: { name: "get_adapter", arguments: { module: "payments", provider: "stripe" } },
|
|
129
|
+
});
|
|
130
|
+
const json = JSON.parse(response);
|
|
131
|
+
const content = JSON.parse(json.result.content[0].text);
|
|
132
|
+
assert.equal(content.name, "stripe");
|
|
133
|
+
assert.equal(content.module, "payments");
|
|
134
|
+
assert.ok(content.config.required.length > 0);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const KNOWN_FLAGS = new Set(["--root", "--strict", "--help", "-h", "--version", "-v", "--output", "--modules", "--format", "--compact", "--quiet", "--module", "--lang", "--name", "--minimal", "--prompts", "--adapter"]);
|
|
2
|
+
const COMMANDS = new Set(["build", "resolve", "list", "inspect", "graph", "search", "adapters", "generate", "prototype", "schema", "verify", "implement", "mcp"]);
|
|
3
|
+
const ADAPTER_SUBCOMMANDS = new Set(["list", "add", "remove", "show", "verify", "search"]);
|
|
4
|
+
const GENERATE_SUBCOMMANDS = new Set(["interfaces", "adapters", "tests", "all"]);
|
|
5
|
+
const LANGUAGES = new Set(["typescript", "rust", "python", "go"]);
|
|
6
|
+
export function parseArguments(args) {
|
|
7
|
+
const parsed = {
|
|
8
|
+
command: "build",
|
|
9
|
+
adapterSubcommand: undefined,
|
|
10
|
+
generateSubcommand: undefined,
|
|
11
|
+
root: undefined,
|
|
12
|
+
strict: undefined,
|
|
13
|
+
help: undefined,
|
|
14
|
+
version: undefined,
|
|
15
|
+
output: undefined,
|
|
16
|
+
modules: [],
|
|
17
|
+
target: undefined,
|
|
18
|
+
query: undefined,
|
|
19
|
+
provider: undefined,
|
|
20
|
+
module: undefined,
|
|
21
|
+
language: undefined,
|
|
22
|
+
format: "ascii",
|
|
23
|
+
compact: false,
|
|
24
|
+
quiet: false,
|
|
25
|
+
minimal: false,
|
|
26
|
+
prompts: false,
|
|
27
|
+
unknown: [],
|
|
28
|
+
};
|
|
29
|
+
let i = 0;
|
|
30
|
+
if (i < args.length && !args[i].startsWith("-") && COMMANDS.has(args[i])) {
|
|
31
|
+
parsed.command = args[i];
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
if (parsed.command === "adapters" && i < args.length && !args[i].startsWith("-") && ADAPTER_SUBCOMMANDS.has(args[i])) {
|
|
35
|
+
parsed.adapterSubcommand = args[i];
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
if (parsed.command === "generate" && i < args.length && !args[i].startsWith("-") && GENERATE_SUBCOMMANDS.has(args[i])) {
|
|
39
|
+
parsed.generateSubcommand = args[i];
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
if (parsed.command === "adapters" && parsed.adapterSubcommand === "add" && i < args.length && !args[i].startsWith("-")) {
|
|
43
|
+
parsed.provider = args[i];
|
|
44
|
+
i++;
|
|
45
|
+
if (i < args.length && !args[i].startsWith("-")) {
|
|
46
|
+
parsed.module = args[i];
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (parsed.command === "adapters" && parsed.adapterSubcommand === "remove" && i < args.length && !args[i].startsWith("-")) {
|
|
51
|
+
parsed.module = args[i];
|
|
52
|
+
i++;
|
|
53
|
+
}
|
|
54
|
+
if (parsed.command === "adapters" && (parsed.adapterSubcommand === "list" || parsed.adapterSubcommand === "verify" || parsed.adapterSubcommand === "search") && i < args.length && !args[i].startsWith("-")) {
|
|
55
|
+
parsed.query = args[i];
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
if (parsed.command === "generate" && i < args.length && !args[i].startsWith("-")) {
|
|
59
|
+
parsed.provider = args[i];
|
|
60
|
+
i++;
|
|
61
|
+
if (i < args.length && !args[i].startsWith("-")) {
|
|
62
|
+
parsed.module = args[i];
|
|
63
|
+
i++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if ((parsed.command === "inspect" || parsed.command === "graph") && i < args.length && !args[i].startsWith("-")) {
|
|
67
|
+
parsed.target = args[i];
|
|
68
|
+
i++;
|
|
69
|
+
}
|
|
70
|
+
if (parsed.command === "verify" && i < args.length && !args[i].startsWith("-")) {
|
|
71
|
+
parsed.target = args[i];
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
if (parsed.command === "search" && i < args.length && !args[i].startsWith("-")) {
|
|
75
|
+
parsed.query = args[i];
|
|
76
|
+
i++;
|
|
77
|
+
}
|
|
78
|
+
for (; i < args.length; i++) {
|
|
79
|
+
const arg = args[i];
|
|
80
|
+
if (arg === "--root" && i + 1 < args.length) {
|
|
81
|
+
parsed.root = args[++i];
|
|
82
|
+
}
|
|
83
|
+
else if (arg === "--strict") {
|
|
84
|
+
parsed.strict = true;
|
|
85
|
+
}
|
|
86
|
+
else if (arg === "--help" || arg === "-h") {
|
|
87
|
+
parsed.help = true;
|
|
88
|
+
}
|
|
89
|
+
else if (arg === "--version" || arg === "-v") {
|
|
90
|
+
parsed.version = true;
|
|
91
|
+
}
|
|
92
|
+
else if (arg === "--output" && i + 1 < args.length) {
|
|
93
|
+
parsed.output = args[++i];
|
|
94
|
+
}
|
|
95
|
+
else if (arg === "--modules" && i + 1 < args.length) {
|
|
96
|
+
parsed.modules = (args[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
97
|
+
}
|
|
98
|
+
else if (arg === "--module" && i + 1 < args.length) {
|
|
99
|
+
parsed.module = args[++i];
|
|
100
|
+
}
|
|
101
|
+
else if (arg === "--lang" && i + 1 < args.length) {
|
|
102
|
+
const lang = args[++i] ?? "";
|
|
103
|
+
if (LANGUAGES.has(lang)) {
|
|
104
|
+
parsed.language = lang;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
parsed.unknown.push(arg);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (arg === "--format" && i + 1 < args.length) {
|
|
111
|
+
const fmt = args[++i];
|
|
112
|
+
if (fmt === "ascii" || fmt === "mermaid") {
|
|
113
|
+
parsed.format = fmt;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
parsed.unknown.push(arg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else if (arg === "--compact") {
|
|
120
|
+
parsed.compact = true;
|
|
121
|
+
}
|
|
122
|
+
else if (arg === "--quiet") {
|
|
123
|
+
parsed.quiet = true;
|
|
124
|
+
}
|
|
125
|
+
else if (arg === "--minimal") {
|
|
126
|
+
parsed.minimal = true;
|
|
127
|
+
}
|
|
128
|
+
else if (arg === "--prompts") {
|
|
129
|
+
parsed.prompts = true;
|
|
130
|
+
}
|
|
131
|
+
else if (arg === "--adapter" && i + 1 < args.length) {
|
|
132
|
+
parsed.provider = args[++i];
|
|
133
|
+
}
|
|
134
|
+
else if (arg === "--name" && i + 1 < args.length) {
|
|
135
|
+
parsed.target = args[++i];
|
|
136
|
+
}
|
|
137
|
+
else if (arg.startsWith("-") && !KNOWN_FLAGS.has(arg)) {
|
|
138
|
+
parsed.unknown.push(arg);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@friehub/blueprint",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A domain contract catalogue for backend development. 108 interfaces, 83 adapters, AI-ready.",
|
|
5
|
+
"keywords": ["contract", "domain", "backend", "ai", "code-generation", "adapter", "blueprint"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js",
|
|
11
|
+
"./cli": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"blueprint": "./dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"blueprinter": "./dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"check": "tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"test": "npm run build && node --test dist/core/*.test.js dist/core/adapters/*.test.js",
|
|
23
|
+
"test:mcp": "node --test dist/mcp/*.test.js",
|
|
24
|
+
"test:integration": "npm run build && bash scripts/integration-test.sh",
|
|
25
|
+
"postinstall": "npm run build",
|
|
26
|
+
"link": "npm run build && npm link"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.15.29",
|
|
30
|
+
"typescript": "^5.8.3"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
|
+
"@types/handlebars": "^4.0.40",
|
|
35
|
+
"handlebars": "^4.7.9",
|
|
36
|
+
"yaml": "^2.9.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"exactOptionalPropertyTypes": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"outDir": "dist"
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*.ts"]
|
|
16
|
+
}
|