Initial commit: Product OS platform

- Control Plane API with Gemini integration
- Executors: Deploy, Analytics, Marketing
- MCP Adapter for Continue integration
- VSCode/VSCodium extension
- Tool registry and run tracking
- In-memory storage for local dev
- Terraform infrastructure setup
This commit is contained in:
2026-01-19 20:34:43 -08:00
commit b6d7148ded
58 changed files with 5365 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
{
"name": "@productos/marketing-executor",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/index.js"
},
"dependencies": {
"@fastify/cors": "^10.0.0",
"@fastify/sensible": "^6.0.0",
"fastify": "^5.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
}
}

View File

@@ -0,0 +1,88 @@
import Fastify from "fastify";
import cors from "@fastify/cors";
import sensible from "@fastify/sensible";
const app = Fastify({ logger: true });
await app.register(cors, { origin: true });
await app.register(sensible);
app.get("/healthz", async () => ({ ok: true, executor: "marketing" }));
/**
* Generate channel posts from a brief
* In production: calls Gemini API
*/
app.post("/execute/marketing/generate", async (req) => {
const body = req.body as any;
const { input } = body;
console.log(`✍️ Generate posts:`, input.brief?.goal);
await new Promise(r => setTimeout(r, 1000)); // Simulate AI generation time
const channels = (input.channels ?? ["x", "linkedin"]).map((channel: string) => ({
channel,
posts: [
{
text: `🚀 Exciting news! ${input.brief?.product ?? "Our product"} just got even better. ${input.brief?.key_points?.[0] ?? "Check it out!"}\n\n${input.brief?.call_to_action ?? "Learn more"} 👇\n${input.brief?.landing_page_url ?? "https://example.com"}`,
hashtags: ["#ProductUpdate", "#SaaS", "#Innovation"],
media_suggestions: ["product-screenshot.png", "feature-demo.gif"]
},
{
text: `${input.brief?.audience ?? "Teams"} asked, we listened! Introducing ${input.brief?.product ?? "new features"} that will transform how you work.\n\n✨ ${input.brief?.key_points?.join("\n✨ ") ?? "Amazing new capabilities"}\n\nTry it today!`,
hashtags: ["#ProductLaunch", "#Productivity"],
media_suggestions: ["comparison-chart.png"]
},
{
text: `Did you know? ${input.brief?.key_points?.[0] ?? "Our latest update"} can save you hours every week.\n\nHere's how ${input.brief?.product ?? "it"} works:\n1⃣ Set it up in minutes\n2⃣ Let automation do the work\n3⃣ Focus on what matters\n\n${input.brief?.offer ?? "Start free today!"}`,
hashtags: ["#Automation", "#WorkSmarter"],
media_suggestions: ["how-it-works.mp4"]
}
]
}));
return {
channels,
brief_summary: input.brief?.goal,
generated_at: new Date().toISOString(),
variations_per_channel: 3
};
});
/**
* Get brand profile
*/
app.post("/execute/brand/get", async (req) => {
const body = req.body as any;
const { input } = body;
console.log(`🎨 Get brand profile:`, input.profile_id);
return {
profile_id: input.profile_id ?? "default",
brand: {
name: "Product OS",
voice: {
tone: ["professional", "friendly", "innovative"],
style_notes: ["Use active voice", "Keep sentences short", "Be specific with benefits"],
do: ["Use data and metrics", "Include calls to action", "Highlight customer success"],
dont: ["Make unverified claims", "Use jargon", "Be overly salesy"]
},
audience: {
primary: "SaaS founders and product teams",
secondary: "Growth marketers and developers"
},
claims_policy: {
forbidden_claims: ["#1 in the market", "Guaranteed results"],
required_disclaimers: [],
compliance_notes: ["All metrics should be verifiable"]
}
}
};
});
const port = Number(process.env.PORT ?? 8093);
app.listen({ port, host: "0.0.0.0" }).then(() => {
console.log(`📣 Marketing Executor running on http://localhost:${port}`);
});

View File

@@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"types": ["node"]
}
}