From 92e5fbbbcce7027473d756912e25576b0ce89c0a Mon Sep 17 00:00:00 2001 From: cyber-turtle Date: Fri, 1 May 2026 17:42:31 +0100 Subject: [PATCH 01/24] chore: reorganize forge to satisfy repository standards --- kits/forge/README.md | 214 + kits/forge/agent.md | 119 + kits/forge/apps/.env.example | 17 + kits/forge/apps/.gitignore | 28 + kits/forge/apps/actions/orchestrate.ts | 49 + kits/forge/apps/app/api/flow/route.ts | 72 + kits/forge/apps/app/globals.css | 436 + kits/forge/apps/app/icon.svg | 1 + kits/forge/apps/app/layout.tsx | 24 + kits/forge/apps/app/new/page.tsx | 52 + kits/forge/apps/app/page.tsx | 169 + kits/forge/apps/app/preview/contract/page.tsx | 130 + kits/forge/apps/app/preview/invoice/page.tsx | 130 + .../apps/components/AuroraBackground.tsx | 77 + kits/forge/apps/components/GalaxyButton.tsx | 107 + kits/forge/apps/components/Nav.tsx | 21 + .../components/preview/ContractDocument.tsx | 98 + .../apps/components/preview/ExportButton.tsx | 86 + .../components/preview/InvoiceDocument.tsx | 186 + .../components/preview/SignatureCanvas.tsx | 129 + .../components/wizard/Step1ProjectDetails.tsx | 403 + .../apps/components/wizard/Step2Pricing.tsx | 276 + .../components/wizard/Step3GoverningLaw.tsx | 224 + .../apps/components/wizard/Step4Generate.tsx | 215 + .../apps/components/wizard/StepIndicator.tsx | 76 + kits/forge/apps/lib/lamatic-client.ts | 27 + kits/forge/apps/lib/lamatic.ts | 9 + kits/forge/apps/lib/storage.ts | 18 + kits/forge/apps/lib/types.ts | 102 + kits/forge/apps/lib/utils.ts | 6 + kits/forge/apps/next.config.mjs | 11 + kits/forge/apps/orchestrate.js | 92 + kits/forge/apps/package-lock.json | 7567 +++++++++++++++++ kits/forge/apps/package.json | 36 + kits/forge/apps/postcss.config.js | 7 + kits/forge/apps/public/forge2.svg | 1 + kits/forge/apps/public/screenshot.png | Bin 0 -> 1024314 bytes kits/forge/apps/tailwind.config.js | 57 + kits/forge/apps/tsconfig.json | 27 + kits/forge/constitutions/default.md | 16 + kits/forge/flows/forge-contract.ts | 137 + kits/forge/flows/forge-invoice.ts | 137 + kits/forge/flows/forge-pricing.ts | 137 + kits/forge/flows/forge-tradeoff.ts | 137 + kits/forge/lamatic.config.ts | 38 + ..._llmnode-contract_generative-model-name.ts | 5 + ...e_llmnode-invoice_generative-model-name.ts | 5 + ...g_llmnode-pricing_generative-model-name.ts | 5 + ..._llmnode-tradeoff_generative-model-name.ts | 5 + ...orge-contract_llmnode-contract_system_0.md | 53 + .../forge-contract_llmnode-contract_user_1.md | 17 + .../forge-invoice_llmnode-invoice_system_0.md | 48 + .../forge-invoice_llmnode-invoice_user_1.md | 17 + .../forge-pricing_llmnode-pricing_system_0.md | 39 + .../forge-pricing_llmnode-pricing_user_1.md | 9 + ...orge-tradeoff_llmnode-tradeoff_system_0.md | 10 + .../forge-tradeoff_llmnode-tradeoff_user_1.md | 10 + 57 files changed, 12124 insertions(+) create mode 100644 kits/forge/README.md create mode 100644 kits/forge/agent.md create mode 100644 kits/forge/apps/.env.example create mode 100644 kits/forge/apps/.gitignore create mode 100644 kits/forge/apps/actions/orchestrate.ts create mode 100644 kits/forge/apps/app/api/flow/route.ts create mode 100644 kits/forge/apps/app/globals.css create mode 100644 kits/forge/apps/app/icon.svg create mode 100644 kits/forge/apps/app/layout.tsx create mode 100644 kits/forge/apps/app/new/page.tsx create mode 100644 kits/forge/apps/app/page.tsx create mode 100644 kits/forge/apps/app/preview/contract/page.tsx create mode 100644 kits/forge/apps/app/preview/invoice/page.tsx create mode 100644 kits/forge/apps/components/AuroraBackground.tsx create mode 100644 kits/forge/apps/components/GalaxyButton.tsx create mode 100644 kits/forge/apps/components/Nav.tsx create mode 100644 kits/forge/apps/components/preview/ContractDocument.tsx create mode 100644 kits/forge/apps/components/preview/ExportButton.tsx create mode 100644 kits/forge/apps/components/preview/InvoiceDocument.tsx create mode 100644 kits/forge/apps/components/preview/SignatureCanvas.tsx create mode 100644 kits/forge/apps/components/wizard/Step1ProjectDetails.tsx create mode 100644 kits/forge/apps/components/wizard/Step2Pricing.tsx create mode 100644 kits/forge/apps/components/wizard/Step3GoverningLaw.tsx create mode 100644 kits/forge/apps/components/wizard/Step4Generate.tsx create mode 100644 kits/forge/apps/components/wizard/StepIndicator.tsx create mode 100644 kits/forge/apps/lib/lamatic-client.ts create mode 100644 kits/forge/apps/lib/lamatic.ts create mode 100644 kits/forge/apps/lib/storage.ts create mode 100644 kits/forge/apps/lib/types.ts create mode 100644 kits/forge/apps/lib/utils.ts create mode 100644 kits/forge/apps/next.config.mjs create mode 100644 kits/forge/apps/orchestrate.js create mode 100644 kits/forge/apps/package-lock.json create mode 100644 kits/forge/apps/package.json create mode 100644 kits/forge/apps/postcss.config.js create mode 100644 kits/forge/apps/public/forge2.svg create mode 100644 kits/forge/apps/public/screenshot.png create mode 100644 kits/forge/apps/tailwind.config.js create mode 100644 kits/forge/apps/tsconfig.json create mode 100644 kits/forge/constitutions/default.md create mode 100644 kits/forge/flows/forge-contract.ts create mode 100644 kits/forge/flows/forge-invoice.ts create mode 100644 kits/forge/flows/forge-pricing.ts create mode 100644 kits/forge/flows/forge-tradeoff.ts create mode 100644 kits/forge/lamatic.config.ts create mode 100644 kits/forge/model-configs/forge-contract_llmnode-contract_generative-model-name.ts create mode 100644 kits/forge/model-configs/forge-invoice_llmnode-invoice_generative-model-name.ts create mode 100644 kits/forge/model-configs/forge-pricing_llmnode-pricing_generative-model-name.ts create mode 100644 kits/forge/model-configs/forge-tradeoff_llmnode-tradeoff_generative-model-name.ts create mode 100644 kits/forge/prompts/forge-contract_llmnode-contract_system_0.md create mode 100644 kits/forge/prompts/forge-contract_llmnode-contract_user_1.md create mode 100644 kits/forge/prompts/forge-invoice_llmnode-invoice_system_0.md create mode 100644 kits/forge/prompts/forge-invoice_llmnode-invoice_user_1.md create mode 100644 kits/forge/prompts/forge-pricing_llmnode-pricing_system_0.md create mode 100644 kits/forge/prompts/forge-pricing_llmnode-pricing_user_1.md create mode 100644 kits/forge/prompts/forge-tradeoff_llmnode-tradeoff_system_0.md create mode 100644 kits/forge/prompts/forge-tradeoff_llmnode-tradeoff_user_1.md diff --git a/kits/forge/README.md b/kits/forge/README.md new file mode 100644 index 00000000..ee90d2cb --- /dev/null +++ b/kits/forge/README.md @@ -0,0 +1,214 @@ +# Forge - AI Contract & Invoice Generator + +> Enter your project details once. Get AI-calibrated pricing. Select a governing law. Walk away with a professional contract and matching invoice - signed and exported as PDFs, in minutes. + +![Built with Lamatic](https://img.shields.io/badge/Built%20with-Lamatic-5B21B6?style=flat-square) +![Next.js 14](https://img.shields.io/badge/Next.js-14-000000?style=flat-square) +![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?style=flat-square) +![agentkit-challenge](https://img.shields.io/badge/challenge-agentkit-F59E0B?style=flat-square) + +[**Live Demo →**](#) _(deploy to Vercel and update this link)_ + +![Forge Screenshot](public/screenshot.png) + +--- + +## 1. The Problem + +Cross-border freelancers, especially in Africa, South Asia, and Latin America, routinely work without professional contracts. They send vague invoices, have no governing law clause, and price their services based on gut feeling rather than market data. When disputes arise, they have zero legal protection. + +Hiring a lawyer to draft a cross-border services agreement costs $500–$2,000. Most freelancers skip it entirely. + +--- + +## 2. The Approach + +Forge completely automates the creation of professional freelance contracts and invoices using 4 Lamatic AI flows in a guided wizard: + +**Flow 1 - Pricing Analysis (`forge-pricing`):** +Takes the freelancer's field, experience level, country, and deliverables - returns AI-calibrated per-item pricing with market context and justification for each rate. The freelancer can edit rates before confirming. + +**Flow 2 - Governing Law Tradeoff (`forge-tradeoff`):** +Analyses both parties' countries, the payment structure, and the freelancer's primary concern (IP, getting paid, scope creep, disputes). Returns 3 governing law options with pros, cons, and a recommendation. + +**Flow 3 - Contract Generation (`forge-contract`):** +Takes all accumulated data - project details, confirmed pricing, chosen governing law - and generates a full 13-section services agreement: parties, recitals, scope, timeline, payment terms, IP, confidentiality, revision policy, late payment, termination, governing law, dispute resolution, and signatures. + +**Flow 4 - Invoice Generation (`forge-invoice`):** +Generates a professional invoice with the confirmed line items, freelancer/client details, payment instructions, and notes referencing the contract date. + +Both documents can be **signed** with an e-signature canvas and **exported as multi-page PDFs**. + +--- + +## 3. The Result + +A freelancer fills out one form, waits ~60 seconds, and receives a contract and invoice that would have taken a lawyer hours to draft. The documents are tailored to their specific jurisdiction, payment method, and experience level. No auth, no database, no subscription - just Lamatic as the backend. + +--- + +## 4. Tradeoffs & Assumptions + +**Tradeoffs:** +- **No persistent storage:** All state lives in `localStorage`. Closing the browser loses the session. This keeps the architecture simple (no database, no auth) but means the tool is session-based. +- **API key security vs. simplicity:** Flow IDs use `NEXT_PUBLIC_` prefix (visible to the browser) since they aren't secrets. The actual `LAMATIC_API_KEY` stays server-side and is proxied through `/api/flow`. +- **PDF quality:** We use `html2canvas` + `jsPDF` for export. This produces good results for most documents but isn't pixel-perfect like a server-side PDF engine (e.g., Puppeteer). The tradeoff is zero server infrastructure. + +**Assumptions:** +- The user is a freelancer doing remote/digital work (code, design, writing, consulting). Physical trades or heavily regulated industries may need more specialised contract language. +- The AI-generated contract is a strong starting point, not a substitute for legal advice on high-stakes engagements. + +--- + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Flow orchestration | **Lamatic.ai** (4 flows) | +| Frontend | **Next.js 14** (App Router, TypeScript) | +| Styling | **Tailwind CSS** | +| PDF export | **jsPDF** + **html2canvas** | +| E-signature | **react-signature-canvas** | +| Icons | **lucide-react** | + +--- + +## Prerequisites + +- Node.js 18+ and npm 9+ +- A [Lamatic.ai](https://lamatic.ai) account with all 4 Forge flows deployed +- Vercel account (optional, for deployment) + +--- + +## Setup Instructions + +1. **Clone and navigate to the kit folder** + ```bash + git clone https://github.com/Lamatic/AgentKit.git + cd AgentKit/kits/forge/apps + ``` + +2. **Install dependencies** + ```bash + npm install + ``` + +3. **Set up environment variables** + ```bash + cp .env.example .env.local + ``` + Then fill in `.env.local` with your real values (see table below). + +4. **Start the development server** + ```bash + npm run dev + ``` + +5. **Open your browser** + ```text + http://localhost:3000 + ``` + +--- + +## Environment Variables + +| Variable | Description | Where to Find | +|----------|-------------|--------------| +| `NEXT_PUBLIC_LAMATIC_ENDPOINT` | Your Lamatic GraphQL API endpoint | Lamatic Studio → Settings → API Docs | +| `NEXT_PUBLIC_LAMATIC_PROJECT_ID` | Your Lamatic project ID | Lamatic Studio → Settings → Project | +| `LAMATIC_API_KEY` | Your API key (server-only, never exposed) | Lamatic Studio → Settings → API Keys | +| `NEXT_PUBLIC_FLOW_PRICING` | Forge Pricing flow ID | Lamatic Studio → forge-pricing → ⋮ → Flow ID | +| `NEXT_PUBLIC_FLOW_TRADEOFF` | Forge Tradeoff flow ID | Lamatic Studio → forge-tradeoff → ⋮ → Flow ID | +| `NEXT_PUBLIC_FLOW_CONTRACT` | Forge Contract flow ID | Lamatic Studio → forge-contract → ⋮ → Flow ID | +| `NEXT_PUBLIC_FLOW_INVOICE` | Forge Invoice flow ID | Lamatic Studio → forge-invoice → ⋮ → Flow ID | + +> ⚠️ `LAMATIC_API_KEY` must NEVER be exposed client-side. All Lamatic calls go through the server-side API route at `/api/flow`. + +--- + +## Flows + +### Pricing Flow (`forge-pricing`) +**Input:** `{ work_type, field, experience_level, years_of_experience, deliverables, payment_structure, currency, freelancer_country, client_country }` +**Output:** `{ pricing: string (JSON) }` → parsed to `{ experience_assessment, market_context, line_items[], total_amount, currency }` + +Analyses the freelancer's experience and geography against market data, returns per-item suggested pricing with justification. + +### Tradeoff Flow (`forge-tradeoff`) +**Input:** `{ freelancer_name, freelancer_country, freelancer_payment_method, freelancer_primary_concern, client_name, client_country, client_type, project_title, project_description, deliverables, timeline_start, timeline_end, payment_amount, payment_currency, payment_structure, work_type }` +**Output:** `{ options: string (JSON) }` → parsed to `Array<{ option_name, explanation, pros[], cons[], recommended }>` + +Returns 3 governing law options with pros, cons, and a recommendation based on both parties' jurisdictions. + +### Contract Flow (`forge-contract`) +**Input:** All project details + `payment_amount`, `payment_currency`, `chosen_governing_law` +**Output:** `{ contract: string (JSON) }` → parsed to `Record` + +Generates a full 13-section services agreement: parties, recitals, scope, timeline, payment, IP, confidentiality, revisions, late payment, termination, governing law, dispute resolution, signatures. + +### Invoice Flow (`forge-invoice`) +**Input:** Freelancer/client details + `line_items` (stringified JSON), `currency`, `total_amount`, `payment_instructions`, `notes` +**Output:** `{ invoice: string (JSON) }` → parsed to `{ header, freelancer, client, line_items[], totals, payment_instructions, notes }` + +Generates a formatted invoice matching the confirmed pricing from the contract. + +> **Important:** All flow responses come back as `result.result.fieldName` where `fieldName` is a JSON string that must be parsed with `JSON.parse()`. + +--- + +## Project Structure + +``` +kits/forge/ +├── lamatic.config.ts # REQUIRED: project metadata +├── agent.md # REQUIRED: agent identity doc +├── README.md # Root setup guide +├── constitutions/ # Guardrails +├── flows/ # Flow definitions +└── apps/ # Next.js App + ├── app/ + ├── components/ + ├── actions/ + ├── lib/ + └── package.json +``` + +--- + +## Deploy to Vercel + +1. Push your branch to GitHub: + ```bash + git checkout -b feat/forge + git add kits/agentic/forge/ + git commit -m "feat: Add Forge - AI Contract & Invoice Generator" + git push origin feat/forge + ``` + +2. Go to [vercel.com](https://vercel.com) and import your repo + +3. Set the **root directory** to `kits/forge/apps` + +4. Add all environment variables from the table above + +5. Click **Deploy** + +--- + +## Live Preview + +_Coming soon_ + +--- + +## Contributing + +This kit was built for the [mission-possible](https://github.com/Lamatic/AgentKit). To open a PR: + +```text +github.com/Lamatic/AgentKit/compare/main...YOUR-USERNAME:feat/forge?expand=1 +``` + +Add the `mission-possible` label to your PR. diff --git a/kits/forge/agent.md b/kits/forge/agent.md new file mode 100644 index 00000000..cdd761f8 --- /dev/null +++ b/kits/forge/agent.md @@ -0,0 +1,119 @@ +# Forge + +## Overview +Forge is an AI-powered document generation agent specifically engineered for cross-border freelancers. It addresses the massive legal and financial gap faced by independent contractors who often operate without formal services agreements due to high legal fees and jurisdictional complexity. + +Forge uses a multi-flow guided wizard architecture (4 distinct Lamatic flows) to automate the entire onboarding-to-billing lifecycle. It doesn't just "fill templates"; it performs AI-calibrated market analysis for pricing, evaluates international jurisdictional tradeoffs for governing law selection, and synthesizes complete, legally-structured 13-section contracts and matching invoices. The system is designed to be session-based and stateless, utilizing local storage for temporary persistence and Lamatic AgentKit flows for the core intelligence. + +--- + +## Purpose +Forge exists to democratize access to professional legal and financial documentation for the global freelance economy. Many freelancers, particularly in emerging markets, price their services based on intuition and work without the protection of a governing law clause. Forge solves this by providing: + +1. **Market Calibration**: An AI flow that suggests rates based on global market data, freelancer location, and deliverables. +2. **Jurisdictional Risk Mitigation**: A tradeoff analysis flow that helps freelancers choose between their local law, the client's law, or a neutral third jurisdiction (like Delaware or England & Wales). +3. **Execution Ready Documents**: High-fidelity, signed, and exportable PDFs that would otherwise cost thousands of dollars in legal fees. + +By the end of a Forge session, a freelancer has a professional contract and a matching invoice, significantly reducing the risk of payment disputes and scope creep. + +--- + +## Flows + +### `Forge - Pricing Analysis` + +- **Trigger** + - Invoked via a GraphQL request when the user completes Step 1 (Project Details). +- **What it does** + 1. Receives data about the work type, field, deliverables, and both parties' countries. + 2. Analyzes the freelancer's experience level and geography against global market benchmarks. + 3. Returns a structured pricing breakdown with market context and justifications for each rate. +- **When to use this flow** + - To provide the user with data-driven confidence in their pricing before generating a contract. +- **Output** + - A JSON object containing `experience_assessment`, `market_context`, `line_items[]` (with suggested rates), and `total_amount`. + +### `Forge - Tradeoff Analysis` + +- **Trigger** + - Invoked when the user selects their primary concern (IP, Getting Paid, etc.) in Step 3. +- **What it does** + 1. Compares the legal jurisdictions of the freelancer and the client. + 2. Evaluates the specific payment structure and project deliverables. + 3. Proposes three distinct governing law options tailored to the user's primary concern. +- **When to use this flow** + - To help users decide which country's laws should govern their services agreement. +- **Output** + - An array of `options` containing `option_name`, `explanation`, `pros[]`, `cons[]`, and a `recommended` flag. + +### `Forge - Contract Generation` + +- **Trigger** + - Invoked in Step 4 after the user confirms all pricing and legal choices. +- **What it does** + 1. Synthesizes every data point collected throughout the wizard. + 2. Generates a full 13-section Services Agreement including: Parties, Recitals, Scope, Timeline, Payment, IP, Confidentiality, Revisions, Late Payment, Termination, Governing Law, Dispute Resolution, and Signatures. +- **When to use this flow** + - To create the final legal document for the engagement. +- **Output** + - A detailed JSON record where each key is a section heading and the value contains the legal body text. + +### `Forge - Invoice Generation` + +- **Trigger** + - Invoked simultaneously with or immediately after the contract generation. +- **What it does** + 1. Translates the confirmed line items and pricing from the contract into a standard invoice format. + 2. Adds professional headers, freelancer/client contact info, and payment instructions. +- **When to use this flow** + - To generate the corresponding billable document for the project. +- **Output** + - A formatted invoice JSON containing `freelancer`, `client`, `line_items[]`, `totals`, and `notes`. + +--- + +## Guardrails +- **Legal Advice**: Forge must include a disclaimer that it is a decision-support tool, not a law firm. +- **PII Handling**: Personal details (names, emails) are processed but not persisted long-term in the vector store; they exist only in session memory. +- **Integrity**: Generated rates must be justified with market context to prevent arbitrary pricing. +- **Structure**: Contracts must never omit critical "kill clauses" like termination or IP ownership. + +--- + +## Integration Reference + +| Integration | Purpose | Config Key | +|---|---|---| +| Lamatic AgentKit | Orchestrates the 4 document/pricing flows | `NEXT_PUBLIC_FLOW_...` | +| LLM Provider | Synthesis of pricing, tradeoffs, and legal text | Configured in Lamatic Studio | +| PDF Engine | Client-side export of documents | `jsPDF` + `html2canvas` | +| Storage | Session management | `localStorage` | + +--- + +## Environment Setup +- `NEXT_PUBLIC_FLOW_PRICING` — Flow ID for Pricing Analysis. +- `NEXT_PUBLIC_FLOW_TRADEOFF` — Flow ID for Tradeoff Analysis. +- `NEXT_PUBLIC_FLOW_CONTRACT` — Flow ID for Contract Synthesis. +- `NEXT_PUBLIC_FLOW_INVOICE` — Flow ID for Invoice Generation. +- `LAMATIC_API_KEY` — Server-only secret for authenticating flow calls. +- `NEXT_PUBLIC_LAMATIC_ENDPOINT` — Your project's GraphQL endpoint. +- `NEXT_PUBLIC_LAMATIC_PROJECT_ID` — Your Lamatic project ID. + +--- + +## Common Failure Modes + +| Symptom | Likely Cause | Fix | +|---|---|---| +| "Failed to generate pricing" | Invalid field or missing country data | Ensure all Step 1 fields are filled. | +| "Governing law options empty" | Unsupported jurisdiction pair | Select a more standard country or manually input law. | +| PDF exports looking cut off | High resolution or layout shift during render | Ensure the preview container is fully loaded before clicking export. | +| 401 Unauthorized | Server-side proxy missing API Key | Check `LAMATIC_API_KEY` in `.env.local` or Vercel settings. | + +--- + +## Notes +- This is a **Kit** contribution (`type: kit`) with a complete Next.js UI in the `apps/` directory. +- All AI logic is externalized into Lamatic flows to keep the frontend light and maintainable. +- Future improvements include persistent document storage and automated email delivery of signed PDFs. diff --git a/kits/forge/apps/.env.example b/kits/forge/apps/.env.example new file mode 100644 index 00000000..1a432f6d --- /dev/null +++ b/kits/forge/apps/.env.example @@ -0,0 +1,17 @@ +# Lamatic Project Configuration +# Found in: Lamatic Studio → Settings → API Docs → API → Endpoint and use as follows, ([your endpoint]/graphql) +NEXT_PUBLIC_LAMATIC_ENDPOINT="YOUR_LAMATIC_ENDPOINT" + +# Found in: Lamatic Studio → Settings → Project → Project ID +NEXT_PUBLIC_LAMATIC_PROJECT_ID="YOUR_PROJECT_ID" + +# Found in: Lamatic Studio → Settings → API Keys +# IMPORTANT: Keep this server-side only. Do not prefix with NEXT_PUBLIC_ +LAMATIC_API_KEY="YOUR_API_KEY" + +# Forge Flow IDs +# Found in: Lamatic Studio → [Flow Name] → Three-dot menu (⋮) → Flow ID +NEXT_PUBLIC_FLOW_PRICING="YOUR_FORGE_PRICING_FLOW_ID" +NEXT_PUBLIC_FLOW_TRADEOFF="YOUR_FORGE_TRADEOFF_FLOW_ID" +NEXT_PUBLIC_FLOW_CONTRACT="YOUR_FORGE_CONTRACT_FLOW_ID" +NEXT_PUBLIC_FLOW_INVOICE="YOUR_FORGE_INVOICE_FLOW_ID" diff --git a/kits/forge/apps/.gitignore b/kits/forge/apps/.gitignore new file mode 100644 index 00000000..2d816b5b --- /dev/null +++ b/kits/forge/apps/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env +.env.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/kits/forge/apps/actions/orchestrate.ts b/kits/forge/apps/actions/orchestrate.ts new file mode 100644 index 00000000..f14ac7ec --- /dev/null +++ b/kits/forge/apps/actions/orchestrate.ts @@ -0,0 +1,49 @@ +"use server" + +import { getLamaticClient } from "@/lib/lamatic-client" +import { config } from "../orchestrate.js" + +type FlowName = "pricing" | "tradeoff" | "contract" | "invoice"; + +export async function runForgeFlow( + flowName: FlowName, + inputs: Record +): Promise<{ + success: boolean + data?: any + error?: string +}> { + try { + const flow = config.flows[flowName]; + + if (!flow || !flow.workflowId) { + throw new Error(`Flow "${flowName}" not found in configuration`) + } + + const client = getLamaticClient(); + const resData = await client.executeFlow(flow.workflowId, inputs) + + return { + success: true, + data: resData?.result || resData, + } + } catch (error) { + console.error(`[Forge] ${flowName} flow error:`, error) + + let errorMessage = "Unknown error occurred" + if (error instanceof Error) { + errorMessage = error.message + if (error.message.includes("fetch failed")) { + errorMessage = + "Network error: Unable to connect to Lamatic. Please check your internet connection." + } else if (error.message.includes("API key")) { + errorMessage = "Authentication error: Please check your API configuration." + } + } + + return { + success: false, + error: errorMessage, + } + } +} diff --git a/kits/forge/apps/app/api/flow/route.ts b/kits/forge/apps/app/api/flow/route.ts new file mode 100644 index 00000000..88a24f0a --- /dev/null +++ b/kits/forge/apps/app/api/flow/route.ts @@ -0,0 +1,72 @@ +import { NextRequest, NextResponse } from 'next/server'; + +export async function POST(req: NextRequest) { + const { flowId, payload } = await req.json(); + + const endpoint = process.env.NEXT_PUBLIC_LAMATIC_ENDPOINT; + const projectId = process.env.NEXT_PUBLIC_LAMATIC_PROJECT_ID; + const apiKey = process.env.LAMATIC_API_KEY; + + if (!endpoint || !projectId || !apiKey) { + return NextResponse.json( + { error: 'Missing Lamatic environment variables.' }, + { status: 500 } + ); + } + + const graphqlQuery = { + query: `query ExecuteWorkflow($workflowId: String! $payload: JSON!) { + executeWorkflow(workflowId: $workflowId payload: $payload) { + status + result + } + }`, + variables: { workflowId: flowId, payload }, + }; + + try { + const res = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'x-project-id': projectId, + }, + body: JSON.stringify(graphqlQuery), + }); + + const rawText = await res.text(); + console.log('[Forge API] HTTP status:', res.status); + console.log('[Forge API] Raw response:', rawText.slice(0, 500)); + + let data: any; + try { + data = JSON.parse(rawText); + } catch { + return NextResponse.json( + { error: `Lamatic returned non-JSON: ${rawText.slice(0, 200)}` }, + { status: 500 } + ); + } + + if (data?.errors) { + const msg = data.errors[0]?.message ?? 'GraphQL error'; + console.error('[Forge API] GraphQL error:', msg); + return NextResponse.json({ error: msg }, { status: 500 }); + } + + const result = data?.data?.executeWorkflow; + if (!result) { + return NextResponse.json( + { error: `Unexpected response shape: ${JSON.stringify(data).slice(0, 200)}` }, + { status: 500 } + ); + } + + return NextResponse.json(result); + } catch (error) { + console.error('[Forge API] Fetch failed:', error); + const message = error instanceof Error ? error.message : 'Flow execution failed'; + return NextResponse.json({ error: message }, { status: 500 }); + } +} diff --git a/kits/forge/apps/app/globals.css b/kits/forge/apps/app/globals.css new file mode 100644 index 00000000..e37f7d5e --- /dev/null +++ b/kits/forge/apps/app/globals.css @@ -0,0 +1,436 @@ +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Lora:ital,wght@0,400..700;1,400..700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Google+Sans:ital,opsz,wght@0,17..18,400..700;1,17..18,400..700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --bg-base: #0c0c10; + --bg-surface: #14141a; + --bg-elevated: #1c1c24; + --border: #2a2a35; + --accent: #6366f1; + --accent-hover: #4f52e0; + --text-primary: #f0f0f5; + --text-secondary: #8888a0; + --text-muted: #55556a; + --success: #22c55e; + --danger: #ef4444; + --doc-bg: #fafaf8; + --doc-text: #1a1a1a; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Google Sans', 'DM Sans', sans-serif; + font-optical-sizing: auto; + font-variation-settings: "GRAD" 0; + background-color: var(--bg-base); + color: var(--text-primary); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ── Scrollbar ── */ +::-webkit-scrollbar { + width: 6px; +} +::-webkit-scrollbar-track { + background: var(--bg-base); +} +::-webkit-scrollbar-thumb { + background: var(--border); + border-radius: 3px; +} + +/* ── Selections ── */ +::selection { + background: rgba(99, 102, 241, 0.3); + color: var(--text-primary); +} + +/* ── Document rendering (contract/invoice) ── */ +.document-surface { + background-color: var(--doc-bg); + color: var(--doc-text); + font-family: 'Lora', serif; + line-height: 1.8; +} + +.document-surface h1, +.document-surface h2, +.document-surface h3 { + font-family: 'DM Sans', sans-serif; + font-weight: 600; + color: #111; +} + +/* ── Input focus states ── */ +input:focus, +select:focus, +textarea:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15); +} + +/* ── Smooth page transitions ── */ +@keyframes fadeIn { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +.animate-fade-in { + animation: fadeIn 0.4s ease-out forwards; +} + +@keyframes pulse-subtle { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +.animate-pulse-subtle { + animation: pulse-subtle 2s ease-in-out infinite; +} + +/* ── Skeleton loading ── */ +@keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +} + +.skeleton { + background: linear-gradient(90deg, var(--bg-elevated) 25%, var(--border) 50%, var(--bg-elevated) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: 8px; +} + +/* --- LIQUID GLASS (from Founder Lens) --- */ +.liquid-glass { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0.01) 100%); + backdrop-filter: blur(40px) saturate(150%); + -webkit-backdrop-filter: blur(40px) saturate(150%); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: + 0 8px 32px 0 rgba(0, 0, 0, 0.37), + inset 0 1px 1px 0 rgba(255, 255, 255, 0.1); + border-radius: 1.25rem; +} + +.liquid-glass-pill { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.02) 100%); + backdrop-filter: blur(20px) saturate(120%); + border: 1px solid rgba(255, 255, 255, 0.12); + box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.15); + border-radius: 9999px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.liquid-glass-pill:hover { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 100%); + transform: translateY(-1px); +} + +.glass-panel { + background: rgba(255, 255, 255, 0.03); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.06); + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + border-radius: 1.5rem; + transition: all 0.3s ease; +} + +.glass-panel:hover { + transform: translateY(-4px); + border-color: rgba(99, 102, 241, 0.4); + box-shadow: 0 12px 40px rgba(99, 102, 241, 0.15); +} + +/* --- AURORA BG --- */ +.aurora-bg { + position: fixed; + top: 0; left: 0; + width: 100vw; height: 100vh; + z-index: -1; + overflow: hidden; + background-color: var(--bg-base); +} + +/* --- TEXT GRADIENT ANIMATION --- */ +.text-gradient-animate { + background: linear-gradient(to right, #ffffff, #6366f1, #ffffff); + background-size: 200% auto; + color: transparent; + -webkit-background-clip: text; + background-clip: text; + animation: shine 3s linear infinite; +} + +@keyframes shine { + to { + background-position: 200% center; + } +} + +/* --- ICON COLOR ANIMATION --- */ +.animate-icon-color { + animation: iconColor 3s linear infinite; +} + +@keyframes iconColor { + 0%, 100% { color: #ffffff; } + 50% { color: #6366f1; } +} + +/* --- ANIMATED BORDER GLOW --- */ +@property --a { + syntax: ''; + initial-value: 0deg; + inherits: false; +} + +@keyframes spin-angle { + to { + --a: 1turn; + } +} + +.feature-card-glow { + box-sizing: border-box; + border: solid 2px transparent; + border-radius: 1.5rem; + background: + linear-gradient(#14141a, #14141a) padding-box, + linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)) border-box; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.feature-card-glow:hover { + background: + linear-gradient(#14141a, #14141a) padding-box, + conic-gradient(from var(--a, 0deg), transparent 50%, rgba(99,102,241,0.3) 80%, #ffffff 95%, #6366f1 100%) border-box; + animation: spin-angle 2s linear infinite; + transform: translateY(-4px); +} + +/* --- GALAXY BUTTON --- */ +.galaxy-button { + --transition: 0.25s; + --spark: 1.8s; + --hue: 245; + position: relative; + display: inline-block; +} + +.galaxy-btn-core { + --cut: 2px; + --active: 0; + --bg: + radial-gradient( + 120% 120% at 126% 126%, + hsl(var(--hue) calc(var(--active) * 97%) 98% / calc(var(--active) * 0.9)) 40%, + transparent 50% + ) calc(100px - (var(--active) * 100px)) 0 / 100% 100% no-repeat, + radial-gradient( + 120% 120% at 120% 120%, + hsl(var(--hue) calc(var(--active) * 97%) 70% / calc(var(--active) * 1)) 30%, + transparent 70% + ) calc(100px - (var(--active) * 100px)) 0 / 100% 100% no-repeat, + hsl(var(--hue) calc(var(--active) * 100%) calc(12% - (var(--active) * 8%))); + font-weight: 500; + border: 0; + cursor: pointer; + padding: 0.9em 2em; + display: flex; + align-items: center; + gap: 0.5em; + white-space: nowrap; + border-radius: 2rem; + position: relative; + box-shadow: + 0 0 calc(var(--active) * 6em) calc(var(--active) * 3em) hsl(var(--hue) 97% 61% / 0.5), + 0 calc(var(--active) * 0.05em) 0 0 hsl(var(--hue) calc(var(--active) * 97%) calc((var(--active) * 50%) + 30%)) inset, + 0 calc(var(--active) * -0.05em) 0 0 hsl(var(--hue) calc(var(--active) * 97%) calc(var(--active) * 10%)) inset; + transition: box-shadow var(--transition), scale var(--transition); + scale: calc(1 + (var(--active) * 0.05)); + transform-style: preserve-3d; + perspective: 100vmin; + overflow: hidden; + color: white; +} + +.galaxy-btn-core:active { + scale: 1; +} + +.galaxy-star { + height: var(--size); + aspect-ratio: 1; + background: white; + border-radius: 50%; + position: absolute; + opacity: var(--alpha); + top: 50%; + left: 50%; + transform: translate(-50%, -50%) rotate(var(--angle)) rotate(0deg) translateY(var(--distance)); + animation: orbit var(--duration) calc(var(--delay) * -1) infinite linear; +} + +@keyframes orbit { + to { + transform: translate(-50%, -50%) rotate(var(--angle)) rotate(360deg) translateY(var(--distance)); + } +} + +.galaxy { + position: absolute; + width: 100%; + aspect-ratio: 1; + top: 50%; + left: 50%; + translate: -50% -50%; + overflow: hidden; + opacity: var(--active); + transition: opacity var(--transition); +} + +.galaxy__ring { + height: 200%; + width: 200%; + position: absolute; + top: 50%; + left: 50%; + border-radius: 50%; + transform: translate(-28%, -40%) rotateX(-24deg) rotateY(-30deg) rotateX(90deg); + transform-style: preserve-3d; +} + +.galaxy__container { + position: absolute; + inset: 0; + opacity: var(--active); + transition: opacity var(--transition); + mask: radial-gradient(white, transparent); + -webkit-mask: radial-gradient(white, transparent); +} + +.galaxy-star--static { + animation: none; + top: 50%; + left: 50%; + transform: translate(0, 0); + max-height: 4px; + filter: brightness(4); + opacity: 0.9; + animation: + move-x calc(var(--duration) * 0.1) calc(var(--delay) * -0.1) infinite linear, + move-y calc(var(--duration) * 0.2) calc(var(--delay) * -0.2) infinite linear; +} + +.galaxy-button:hover .galaxy-star--static, +.galaxy-button:focus-visible .galaxy-star--static { + animation-play-state: paused; +} + +@keyframes move-x { + 0% { translate: -100px 0; } + 100% { translate: 100px 0; } +} + +@keyframes move-y { + 0% { transform: translate(0, -50px); } + 100% { transform: translate(0, 50px); } +} + +.galaxy-spark-mask { + position: absolute; + inset: 0; + border-radius: 2rem; + padding: var(--cut); + -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + -webkit-mask-composite: xor; + mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); + mask-composite: exclude; + pointer-events: none; +} + +.galaxy-spark { + position: absolute; + inset: 0; + border-radius: inherit; + rotate: 0deg; + overflow: hidden; + mask: linear-gradient(white, transparent 50%); + -webkit-mask: linear-gradient(white, transparent 50%); + animation: flip calc(var(--spark) * 2) infinite steps(2, end); +} + +@keyframes flip { + to { rotate: 360deg; } +} + +.galaxy-spark:before { + content: ""; + position: absolute; + width: 200%; + aspect-ratio: 1; + top: 0%; + left: 50%; + z-index: -1; + translate: -50% -15%; + rotate: 0; + transform: rotate(-90deg); + opacity: calc((var(--active)) + 0.4); + background: conic-gradient( + from 0deg, + transparent 0 340deg, + white 360deg + ); + transition: opacity var(--transition); + animation: rotate var(--spark) linear infinite both; +} + +.galaxy-backdrop { + position: absolute; + inset: 0; + background: rgba(255, 255, 255, 0.05); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 2rem; + transition: background var(--transition), border-color var(--transition); +} + +.galaxy-backdrop::before { + content: ""; + position: absolute; + inset: 0; + border-radius: inherit; + background: var(--bg); + opacity: var(--active); + transition: opacity var(--transition); +} + +@keyframes rotate { + to { transform: rotate(90deg); } +} + +.galaxy-button:hover .galaxy-btn-core, +.galaxy-button:focus-visible .galaxy-btn-core { + --active: 1; + --play-state: running; +} + +.galaxy-text { + letter-spacing: 0.01ch; + color: hsl(0 0% calc(60% + (var(--active) * 26%))); + position: relative; + z-index: 10; +} diff --git a/kits/forge/apps/app/icon.svg b/kits/forge/apps/app/icon.svg new file mode 100644 index 00000000..5a43ffbd --- /dev/null +++ b/kits/forge/apps/app/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/kits/forge/apps/app/layout.tsx b/kits/forge/apps/app/layout.tsx new file mode 100644 index 00000000..b3bd6a43 --- /dev/null +++ b/kits/forge/apps/app/layout.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import "./globals.css"; +import Nav from "@/components/Nav"; + +export const metadata: Metadata = { + title: "Forge - AI Contract & Invoice Generator", + description: + "Generate professional contracts and invoices for cross-border freelance work. AI-powered pricing, governing law analysis, and document generation.", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + +