diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..487ba98 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,83 @@ +name: MindReply Production Deploy + +on: + workflow_dispatch: + +permissions: + contents: read + +env: + VERCEL_ORG_ID: ${{ vars.VERCEL_TEAM_ID || vars.VERCEL_ORG_ID || 'team_0plIJmQLgZC1wVv9zI2eVf3B' }} + VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID || 'prj_EuO1lFvbwoFSdDxBlezNyXG8eVV3' }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN || secrets.VERCEL_ACCESS_TOKEN || secrets.VERCEL_API_TOKEN }} + NEXT_PUBLIC_SITE_URL: ${{ vars.NEXT_PUBLIC_SITE_URL || 'https://www.mind-reply.com' }} + +jobs: + deploy: + name: Build and deploy production + runs-on: ubuntu-latest + timeout-minutes: 20 + concurrency: + group: mindreply-production-deploy-legacy + cancel-in-progress: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm install --no-audit --no-fund + + - name: Typecheck + run: npm run typecheck + + - name: Build locally + run: npm run build + + - name: Guard Vercel token + shell: bash + run: | + if [ -z "$VERCEL_TOKEN" ]; then + echo "Vercel token missing. Add VERCEL_TOKEN, VERCEL_ACCESS_TOKEN, or VERCEL_API_TOKEN in GitHub Actions secrets." + exit 1 + fi + + - name: Install Vercel CLI + run: npm install --global vercel@latest --no-audit --no-fund + + - name: Pull Vercel production environment + run: vercel pull --yes --environment=production --token="$VERCEL_TOKEN" + + - name: Build Vercel production artifact + run: vercel build --prod --token="$VERCEL_TOKEN" + + - name: Deploy to Vercel production + id: deploy + shell: bash + run: | + set -euo pipefail + DEPLOYMENT_URL="$(vercel deploy --prebuilt --prod --token="$VERCEL_TOKEN")" + echo "deployment_url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" + echo "Deployment URL: $DEPLOYMENT_URL" + + - name: Smoke check deployment URL + shell: bash + run: | + set -euo pipefail + DEPLOYMENT_URL="${{ steps.deploy.outputs.deployment_url }}" + test -n "$DEPLOYMENT_URL" + curl --fail --silent --show-error "$DEPLOYMENT_URL/" >/dev/null + curl --fail --silent --show-error "$DEPLOYMENT_URL/agent" >/dev/null + curl --fail --silent --show-error "$DEPLOYMENT_URL/api/health" >/dev/null + + - name: Smoke check production domain + shell: bash + run: | + set -euo pipefail + curl --fail --silent --show-error "$NEXT_PUBLIC_SITE_URL/" >/dev/null + curl --fail --silent --show-error "$NEXT_PUBLIC_SITE_URL/agent" >/dev/null + curl --fail --silent --show-error "$NEXT_PUBLIC_SITE_URL/api/health" >/dev/null diff --git a/.github/workflows/deployment-readiness.yml b/.github/workflows/deployment-readiness.yml index 45c9bde..e865c75 100644 --- a/.github/workflows/deployment-readiness.yml +++ b/.github/workflows/deployment-readiness.yml @@ -29,10 +29,9 @@ jobs: uses: actions/setup-node@v4 with: node-version: "22" - cache: npm - name: Install dependencies - run: npm ci + run: npm install --no-audit --no-fund - name: Verify report state run: npm run report:check @@ -43,6 +42,9 @@ jobs: - name: Verify revenue blueprint run: npm run audit:blueprint + - name: Verify language and SEO positioning + run: npm run seo:i18n:verify + - name: Verify app contract run: npm run verify:all diff --git a/.github/workflows/hourly-owner-report.yml b/.github/workflows/hourly-owner-report.yml index fa2da46..9cf3d46 100644 --- a/.github/workflows/hourly-owner-report.yml +++ b/.github/workflows/hourly-owner-report.yml @@ -1,6 +1,8 @@ name: MindReply Hourly Owner Report on: + schedule: + - cron: "0 * * * *" workflow_dispatch: inputs: channels: @@ -11,8 +13,6 @@ on: description: "Set true to write receipts without sending" required: false default: "false" - schedule: - - cron: "0 * * * *" permissions: contents: read @@ -23,7 +23,7 @@ concurrency: jobs: owner-report: - name: Owner revenue report + name: Generate and deliver owner report runs-on: ubuntu-latest timeout-minutes: 15 env: @@ -31,21 +31,21 @@ jobs: MINDREPLY_REPORT_DRY_RUN: ${{ github.event.inputs.dry_run || vars.MINDREPLY_REPORT_DRY_RUN || 'false' }} MINDREPLY_REPORT_CHANNELS: ${{ github.event.inputs.channels || vars.MINDREPLY_REPORT_CHANNELS || 'email,slack' }} MINDREPLY_REPORT_REQUIRE_LIVE_PROOF: ${{ vars.MINDREPLY_REPORT_REQUIRE_LIVE_PROOF || 'true' }} - MINDREPLY_REPORT_EMAIL: ${{ secrets.MINDREPLY_REPORT_EMAIL || vars.MINDREPLY_REPORT_EMAIL }} + MINDREPLY_REPORT_EMAIL: ${{ secrets.MINDREPLY_REPORT_EMAIL || secrets.OPS_REPORT_EMAIL_TO || vars.MINDREPLY_REPORT_EMAIL || vars.REPORT_TO_EMAIL }} MINDREPLY_REPORT_EMAILS: ${{ secrets.MINDREPLY_REPORT_EMAILS || vars.MINDREPLY_REPORT_EMAILS }} - MINDREPLY_REPORT_FROM: ${{ secrets.MINDREPLY_REPORT_FROM || vars.MINDREPLY_REPORT_FROM }} + MINDREPLY_REPORT_FROM: ${{ secrets.MINDREPLY_REPORT_FROM || secrets.OPS_REPORT_EMAIL_FROM || vars.MINDREPLY_REPORT_FROM || vars.RESEND_FROM }} MINDREPLY_PACKAGE_REQUEST_TO: ${{ secrets.MINDREPLY_PACKAGE_REQUEST_TO || vars.MINDREPLY_PACKAGE_REQUEST_TO }} MINDREPLY_PACKAGE_REQUEST_FROM: ${{ secrets.MINDREPLY_PACKAGE_REQUEST_FROM || vars.MINDREPLY_PACKAGE_REQUEST_FROM }} MINDREPLY_PACKAGE_REQUEST_DRY_RUN: ${{ vars.MINDREPLY_PACKAGE_REQUEST_DRY_RUN || 'false' }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} - MINDREPLY_SLACK_WEBHOOK_URL: ${{ secrets.MINDREPLY_SLACK_WEBHOOK_URL }} + MINDREPLY_SLACK_WEBHOOK_URL: ${{ secrets.MINDREPLY_SLACK_WEBHOOK_URL || secrets.SLACK_WEBHOOK_URL }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + MINDREPLY_SLACK_DM_INVITE_AVAILABLE: ${{ vars.MINDREPLY_SLACK_DM_INVITE_AVAILABLE || 'false' }} NEXT_PUBLIC_SITE_URL: ${{ vars.NEXT_PUBLIC_SITE_URL || 'https://www.mind-reply.com' }} NEXT_PUBLIC_WEBSITE_COMPLETION_PACKAGE_PAYMENT_URL: ${{ vars.NEXT_PUBLIC_WEBSITE_COMPLETION_PACKAGE_PAYMENT_URL }} - VERCEL_PROJECT_ID: ${{ vars.VERCEL_PROJECT_ID || 'prj_EuO1lFvbwoFSdDxBlezNyXG8eVV3' }} - VERCEL_TEAM_ID: ${{ vars.VERCEL_TEAM_ID || 'team_0plIJmQLgZC1wVv9zI2eVf3B' }} VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} - + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID || vars.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID || vars.VERCEL_PROJECT_ID }} steps: - name: Checkout uses: actions/checkout@v4 @@ -53,20 +53,23 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install dependencies - run: npm ci || npm install + run: npm install --no-audit --no-fund - - name: Check report contract + - name: Check report configuration run: npm run report:check - - name: Generate launch report + - name: Generate hourly owner report run: npm run launch:report - name: Verify revenue blueprint run: npm run audit:blueprint + - name: Verify language and SEO positioning + run: npm run seo:i18n:verify + - name: Capture live production revenue surface continue-on-error: true env: @@ -75,10 +78,10 @@ jobs: MINDREPLY_REQUIRE_LIVE_DEPLOYMENT_MATCH: "false" run: npm run verify:live-revenue - - name: Send owner report + - name: Send hourly owner report run: npm run report:send - - name: Upload owner report artifacts + - name: Upload report artifacts if: always() uses: actions/upload-artifact@v4 with: diff --git a/.github/workflows/manual-vercel-production.yml b/.github/workflows/manual-vercel-production.yml index 8f80a09..7edc22b 100644 --- a/.github/workflows/manual-vercel-production.yml +++ b/.github/workflows/manual-vercel-production.yml @@ -19,7 +19,7 @@ jobs: deploy: name: Verified production deploy runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 35 env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN || secrets.VERCEL_ACCESS_TOKEN || secrets.VERCEL_API_TOKEN }} VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID || vars.VERCEL_ORG_ID }} @@ -28,9 +28,9 @@ jobs: MINDREPLY_REPORT_DRY_RUN: ${{ vars.MINDREPLY_REPORT_DRY_RUN || 'false' }} MINDREPLY_REPORT_CHANNELS: ${{ vars.MINDREPLY_REPORT_CHANNELS || 'email,slack' }} MINDREPLY_REPORT_REQUIRE_LIVE_PROOF: true - MINDREPLY_REPORT_SUBJECT: MindReply production deploy recovery report - MINDREPLY_REPORT_EMAIL: ${{ secrets.MINDREPLY_REPORT_EMAIL || secrets.OPS_REPORT_EMAIL_TO || vars.MINDREPLY_REPORT_EMAIL || vars.REPORT_TO_EMAIL }} - MINDREPLY_REPORT_EMAILS: ${{ secrets.MINDREPLY_REPORT_EMAILS || vars.MINDREPLY_REPORT_EMAILS }} + MINDREPLY_REPORT_SUBJECT: MindReply production deploy report + MINDREPLY_REPORT_EMAIL: ${{ secrets.MINDREPLY_REPORT_EMAIL || secrets.OPS_REPORT_EMAIL_TO || vars.MINDREPLY_REPORT_EMAIL || vars.REPORT_TO_EMAIL || 'angellllkr@gmail.com' }} + MINDREPLY_REPORT_EMAILS: ${{ secrets.MINDREPLY_REPORT_EMAILS || vars.MINDREPLY_REPORT_EMAILS || 'angellllkr@gmail.com' }} MINDREPLY_REPORT_FROM: ${{ secrets.MINDREPLY_REPORT_FROM || secrets.OPS_REPORT_EMAIL_FROM || vars.MINDREPLY_REPORT_FROM || vars.RESEND_FROM }} MINDREPLY_REPORT_EMAIL_ALLOWLIST: ${{ secrets.MINDREPLY_REPORT_EMAIL_ALLOWLIST || secrets.OPS_REPORT_EMAIL_ALLOWLIST || vars.REPORT_EMAIL_ALLOWLIST }} RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} @@ -41,18 +41,11 @@ jobs: MINDREPLY_LIVE_REVENUE_JSON: mindreply-live-revenue-surface.json MINDREPLY_EXPECTED_SHA: ${{ github.sha }} MINDREPLY_REQUIRE_LIVE_SHA_MATCH: true + steps: - name: Confirm intentional production deploy run: test "${{ inputs.confirm }}" = "deploy-production" - - name: Require main branch - shell: bash - run: | - if [ "${GITHUB_REF_NAME}" != "main" ]; then - echo "Production deploy is restricted to main. Current ref: ${GITHUB_REF_NAME}" - exit 1 - fi - - name: Checkout uses: actions/checkout@v4 @@ -60,85 +53,141 @@ jobs: uses: actions/setup-node@v4 with: node-version: "22" + cache: npm + + - name: Check live production commit + id: live + shell: bash + run: | + set -euo pipefail + curl --fail --show-error --silent https://www.mind-reply.com/api/version > /tmp/mindreply-version.json || true + live_sha="$(node -e "const fs=require('fs'); try { const v=JSON.parse(fs.readFileSync('/tmp/mindreply-version.json','utf8')); process.stdout.write(v.deployment?.commitSha || ''); } catch { process.stdout.write(''); }")" + echo "live_sha=$live_sha" >> "$GITHUB_OUTPUT" + if [ "$live_sha" = "$GITHUB_SHA" ]; then + echo "current=true" >> "$GITHUB_OUTPUT" + echo "Production already matches $GITHUB_SHA; skipping deploy." + else + echo "current=false" >> "$GITHUB_OUTPUT" + echo "Production is ${live_sha:-unknown}; deploying $GITHUB_SHA." + fi - name: Install dependencies - run: npm install --no-audit --fund=false --progress=false + if: steps.live.outputs.current != 'true' + run: npm ci - name: Check owner report configuration + if: steps.live.outputs.current != 'true' run: npm run report:check - name: Generate owner state report + if: steps.live.outputs.current != 'true' run: npm run launch:report - name: Verify revenue blueprint + if: steps.live.outputs.current != 'true' run: npm run audit:blueprint + - name: Verify language and SEO positioning + if: steps.live.outputs.current != 'true' + run: npm run seo:i18n:verify + - name: Verify app + if: steps.live.outputs.current != 'true' run: npm run verify:all - name: Vercel deploy preflight + if: steps.live.outputs.current != 'true' + shell: bash run: | + set -euo pipefail test -n "$VERCEL_TOKEN" test "$VERCEL_ORG_ID" = "team_0plIJmQLgZC1wVv9zI2eVf3B" test "$VERCEL_PROJECT_ID" = "prj_EuO1lFvbwoFSdDxBlezNyXG8eVV3" - name: Install Vercel CLI + if: steps.live.outputs.current != 'true' run: npm install --global vercel@latest --no-audit --fund=false --progress=false - name: Pull Vercel production environment + if: steps.live.outputs.current != 'true' run: vercel pull --yes --environment=production --token "$VERCEL_TOKEN" - name: Build production artifact + if: steps.live.outputs.current != 'true' + env: + NEXT_PUBLIC_MINDREPLY_BUILD_COMMIT_SHA: ${{ github.sha }} + NEXT_PUBLIC_MINDREPLY_BUILD_BRANCH: ${{ github.ref_name }} + NEXT_PUBLIC_MINDREPLY_BUILD_ENVIRONMENT: production + NEXT_PUBLIC_MINDREPLY_BUILD_URL: https://www.mind-reply.com + NEXT_PUBLIC_MINDREPLY_PROJECT_PRODUCTION_URL: https://www.mind-reply.com run: vercel build --prod --token "$VERCEL_TOKEN" - name: Deploy prebuilt artifact to production - id: vercel_deploy + if: steps.live.outputs.current != 'true' shell: bash run: | set -euo pipefail - DEPLOYMENT_OUTPUT="$(vercel deploy --prebuilt --prod --token "$VERCEL_TOKEN")" - printf '%s\n' "$DEPLOYMENT_OUTPUT" - DEPLOYMENT_URL="$(printf '%s\n' "$DEPLOYMENT_OUTPUT" | grep -Eo 'https://[^[:space:]]+\.vercel\.app' | tail -n 1)" - if [ -z "$DEPLOYMENT_URL" ]; then - echo "Could not parse Vercel deployment URL from deploy output." - exit 1 - fi - DEPLOYMENT_HOST="${DEPLOYMENT_URL#https://}" - echo "deployment_url=$DEPLOYMENT_URL" >> "$GITHUB_OUTPUT" - echo "deployment_host=$DEPLOYMENT_HOST" >> "$GITHUB_OUTPUT" - echo "VERCEL_DEPLOYMENT_URL=$DEPLOYMENT_URL" >> "$GITHUB_ENV" - echo "VERCEL_DEPLOYMENT_HOST=$DEPLOYMENT_HOST" >> "$GITHUB_ENV" + deployment_output="$(vercel deploy --prebuilt --prod --token "$VERCEL_TOKEN" --scope angellllkr-engs-projects)" + echo "$deployment_output" + deployment_url="$(printf '%s\n' "$deployment_output" | grep -Eo 'https://[A-Za-z0-9.-]+\.vercel\.app' | tail -n 1)" + test -n "$deployment_url" + echo "VERCEL_DEPLOYMENT_URL=$deployment_url" >> "$GITHUB_ENV" + echo "Deployment URL: $deployment_url" >> "$GITHUB_STEP_SUMMARY" - name: Assign public production aliases + if: steps.live.outputs.current != 'true' shell: bash run: | set -euo pipefail - test -n "${VERCEL_DEPLOYMENT_URL:-}" - vercel alias set "$VERCEL_DEPLOYMENT_URL" www.mind-reply.com --token "$VERCEL_TOKEN" - vercel alias set "$VERCEL_DEPLOYMENT_URL" mind-reply.com --token "$VERCEL_TOKEN" - vercel alias set "$VERCEL_DEPLOYMENT_URL" mindreply-mr-64b2efc9.vercel.app --token "$VERCEL_TOKEN" || true + test -n "$VERCEL_DEPLOYMENT_URL" + vercel alias set "$VERCEL_DEPLOYMENT_URL" www.mind-reply.com --token "$VERCEL_TOKEN" --scope angellllkr-engs-projects + vercel alias set "$VERCEL_DEPLOYMENT_URL" mind-reply.com --token "$VERCEL_TOKEN" --scope angellllkr-engs-projects + vercel alias set "$VERCEL_DEPLOYMENT_URL" mindreply-mr-64b2efc9.vercel.app --token "$VERCEL_TOKEN" --scope angellllkr-engs-projects || true echo "Assigned production aliases to $VERCEL_DEPLOYMENT_URL" >> "$GITHUB_STEP_SUMMARY" - - name: Verify live production reachability + - name: Wait for public aliases to settle + if: steps.live.outputs.current != 'true' + shell: bash + run: | + set -euo pipefail + for attempt in {1..12}; do + echo "Alias settle probe $attempt/12" + if curl --fail --show-error --silent https://www.mind-reply.com/api/version | tee /tmp/mindreply-version.json; then + if grep -q "${GITHUB_SHA}" /tmp/mindreply-version.json; then + echo "Public version reports expected SHA ${GITHUB_SHA}." + exit 0 + fi + fi + sleep 15 + done + echo "Public alias did not report expected SHA after waiting." + cat /tmp/mindreply-version.json || true + exit 1 + + - name: Verify live production + if: steps.live.outputs.current != 'true' run: | curl --fail --show-error --silent https://www.mind-reply.com/ > /tmp/mindreply-home.html curl --fail --show-error --silent https://www.mind-reply.com/contact > /tmp/mindreply-contact.html + curl --fail --show-error --silent https://www.mind-reply.com/products > /tmp/mindreply-products.html + curl --fail --show-error --silent https://www.mind-reply.com/checkout > /tmp/mindreply-checkout.html curl --fail --show-error --silent https://www.mind-reply.com/api/health > /tmp/mindreply-health.json curl --fail --show-error --silent https://www.mind-reply.com/api/version > /tmp/mindreply-version.json - name: Verify live revenue and privacy surface + if: steps.live.outputs.current != 'true' run: npm run verify:live-revenue >> "$GITHUB_STEP_SUMMARY" - name: Refresh owner deployment report with live proof - if: always() + if: always() && steps.live.outputs.current != 'true' run: npm run launch:report || true - name: Send owner deployment report - if: always() + if: always() && steps.live.outputs.current != 'true' run: npm run report:send || true - name: Upload owner report artifacts - if: always() + if: always() && steps.live.outputs.current != 'true' uses: actions/upload-artifact@v4 with: name: mindreply-manual-deploy-reports diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..5b111c0 --- /dev/null +++ b/.vercelignore @@ -0,0 +1,4 @@ +archive/** +.git/** +.next/cache/** +node_modules/.cache/** diff --git a/README.md b/README.md index 197c097..bc2ba99 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ MINDREPLY_REPORT_AGENT_COUNT=25 When `MINDREPLY_REPORT_REQUIRE_DELIVERY=true`, console output does not count as delivery. At least one Slack or email channel must return `sent`, or the workflow fails loudly. -Slack delivery uses a GitHub secret or deployment secret named `MINDREPLY_SLACK_WEBHOOK_URL`. A Slack app field id such as `Xf0B6WHC2SBH` and a workspace invite link are useful setup context, but they are not enough to send; the runtime needs a webhook URL or a connected Slack write destination. For phone-visible Slack updates, create an incoming webhook in the Mind Reply Slack workspace, point it at the preferred channel or direct-message workflow, and store the webhook URL only as `MINDREPLY_SLACK_WEBHOOK_URL`. +Slack delivery uses a GitHub secret or deployment secret named `MINDREPLY_SLACK_WEBHOOK_URL`. Owner-supplied Slack field IDs and workspace invite links are setup context only, not send credentials, and must not be committed. For phone-visible Slack updates, create an incoming webhook in the Mind Reply Slack workspace, point it at the preferred channel or direct-message workflow, and store the webhook URL only as `MINDREPLY_SLACK_WEBHOOK_URL`. Email delivery uses GitHub or deployment secrets: diff --git a/app/api/agent/route.ts b/app/api/agent/route.ts index f59eb49..d27fd8e 100644 --- a/app/api/agent/route.ts +++ b/app/api/agent/route.ts @@ -6,12 +6,12 @@ export const runtime = "nodejs"; export async function POST(request: Request) { const body = await request.json().catch(() => null); - const { input, source } = extractMRAgentInput(body); + const { input, source, locale } = extractMRAgentInput(body); if (!input) { return NextResponse.json({ error: "Input is required." }, { status: 400 }); } - const result = await prepareMindRead({ input, source }); + const result = await prepareMindRead({ input, source, locale }); return NextResponse.json(result); } diff --git a/app/api/geo-locale/route.ts b/app/api/geo-locale/route.ts index e796be4..3e10a21 100644 --- a/app/api/geo-locale/route.ts +++ b/app/api/geo-locale/route.ts @@ -1,42 +1,5 @@ import { NextRequest, NextResponse } from "next/server"; - -const countryLocale: Record = { - AE: "ar", - AR: "es", - AT: "de", - AU: "en", - BE: "fr", - BG: "bg", - BR: "pt", - CA: "en", - CH: "de", - CL: "es", - CN: "zh", - CO: "es", - DE: "de", - ES: "es", - FR: "fr", - GB: "en", - HK: "zh", - IE: "en", - IN: "hi", - JP: "ja", - KW: "ar", - MX: "es", - MY: "en", - NZ: "en", - OM: "ar", - PT: "pt", - QA: "ar", - SA: "ar", - SG: "en", - TW: "zh", - UA: "uk", - UK: "en", - US: "en", -}; - -const supportedLocales = ["en", "es", "fr", "de", "pt", "ar", "hi", "ja", "zh", "uk", "bg"]; +import { countryLocale, normalizeLocale, supportedLocales } from "@/lib/locales"; const priorityMarkets = [ "United Kingdom", @@ -49,6 +12,7 @@ const priorityMarkets = [ "Brazil", "France", "Spain", + "Ukraine", "Bulgaria", ]; @@ -123,21 +87,22 @@ const marketProfiles = [ demand: "Spanish-language gateway for sales objection and agency follow-up use cases", providerGap: "clear entry into Spanish-speaking markets without overbuilding every route first", }, + { + country: "Ukraine", + locale: "uk", + priority: 10.5, + demand: "operator-heavy market where bilingual communication pressure is common", + providerGap: "trust-first Ukrainian communication support remains under-supplied", + }, { country: "Bulgaria", locale: "bg", - priority: 11, - demand: "Bulgarian professional-services, founders, and client-facing operators needing precise business communication support", - providerGap: "localized Bulgarian decision-support and website-completion positioning is less crowded than broad English AI writing tools", + priority: 10.75, + demand: "EU operator market with bilingual client communication and founder-led service pressure", + providerGap: "Bulgarian-first professional reply and decision-support coverage remains thin", }, ]; -function normalizeLocale(value: string | null) { - if (!value) return "en"; - const locale = value.split(",")[0]?.trim().toLowerCase().split("-")[0] || "en"; - return supportedLocales.includes(locale) ? locale : "en"; -} - export function GET(req: NextRequest) { const country = req.headers.get("x-vercel-ip-country") || @@ -146,15 +111,16 @@ export function GET(req: NextRequest) { "US"; const countryCode = country.toUpperCase(); const browserLocale = normalizeLocale(req.headers.get("accept-language")); - const recommendedLocale = countryLocale[countryCode] || browserLocale; + const mappedLocale = countryLocale[countryCode] || null; + const recommendedLocale = mappedLocale || browserLocale; return NextResponse.json({ - country: countryCode, + country: mappedLocale ? countryCode : "GLOBAL", recommendedLocale, browserLocale, supportedLocales, priorityMarkets, marketProfiles, - source: countryLocale[countryCode] ? "country" : "browser", + source: mappedLocale ? "country" : "browser", }); } diff --git a/app/api/intake/route.ts b/app/api/intake/route.ts index 8c42761..70b608f 100644 --- a/app/api/intake/route.ts +++ b/app/api/intake/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server"; import { buildDecisionResponse, type IntakeSource } from "@/lib/decision-layer"; +import { normalizeLocale } from "@/lib/locales"; const sources = new Set(["manual", "gmail", "calendar", "extension"]); @@ -8,10 +9,11 @@ export async function POST(request: Request) { const input = typeof body?.input === "string" ? body.input : ""; const source = sources.has(body?.source) ? body.source : "manual"; const userId = typeof body?.userId === "string" ? body.userId : undefined; + const locale = normalizeLocale(body?.locale); if (!input.trim()) { return NextResponse.json({ error: "Input is required." }, { status: 400 }); } - return NextResponse.json(buildDecisionResponse({ input, source, userId })); + return NextResponse.json(buildDecisionResponse({ input, source, locale, userId })); } diff --git a/app/api/translate/route.ts b/app/api/translate/route.ts index ccf5e72..209eec3 100644 --- a/app/api/translate/route.ts +++ b/app/api/translate/route.ts @@ -1,18 +1,9 @@ import { NextRequest, NextResponse } from "next/server"; +import { defaultLocale, localeMeta, normalizeLocale, supportedLocales, type LocaleCode } from "@/lib/locales"; -const supportedLocales = new Set(["en", "es", "fr", "de", "pt", "ar", "hi", "ja", "zh", "uk", "bg"]); -const googleLocaleMap: Record = { - en: "en", - es: "es", - fr: "fr", - de: "de", - pt: "pt", - ar: "ar", - hi: "hi", - ja: "ja", +const supportedLocaleSet = new Set(supportedLocales); +const googleLocaleFallbacks: Partial> = { zh: "zh-CN", - uk: "uk", - bg: "bg", }; type RequestBody = { @@ -29,8 +20,8 @@ type GoogleTranslationResponse = { }; function normalizeTarget(value: string | undefined) { - const target = (value || "en").toLowerCase().split("-")[0]; - return supportedLocales.has(target) ? target : "en"; + const target = normalizeLocale(value); + return supportedLocaleSet.has(target) ? target : defaultLocale; } function normalizeTexts(body: RequestBody) { @@ -42,7 +33,7 @@ function normalizeTexts(body: RequestBody) { .map((value) => value.slice(0, 280)); } -function passthrough(target: string, texts: string[], configured: boolean, reason?: string) { +function passthrough(target: LocaleCode, texts: string[], configured: boolean, reason?: string) { return NextResponse.json({ configured, provider: configured ? "google-cloud-translate" : "passthrough", @@ -63,8 +54,10 @@ export async function POST(req: NextRequest) { const target = normalizeTarget(body.target); const texts = normalizeTexts(body); + const configured = Boolean(process.env.GOOGLE_TRANSLATE_API_KEY || process.env.GOOGLE_CLOUD_TRANSLATE_API_KEY); + if (texts.length === 0) return NextResponse.json({ configured: false, target, translated: false, translations: [] }); - if (target === "en") return passthrough(target, texts, Boolean(process.env.GOOGLE_TRANSLATE_API_KEY || process.env.GOOGLE_CLOUD_TRANSLATE_API_KEY), "English selected."); + if (target === defaultLocale) return passthrough(target, texts, configured, "Default language selected."); const apiKey = process.env.GOOGLE_TRANSLATE_API_KEY || process.env.GOOGLE_CLOUD_TRANSLATE_API_KEY || ""; if (!apiKey) return passthrough(target, texts, false, "GOOGLE_TRANSLATE_API_KEY is not configured."); @@ -79,8 +72,8 @@ export async function POST(req: NextRequest) { signal: controller.signal, body: JSON.stringify({ q: texts, - target: googleLocaleMap[target] || target, - source: "en", + target: googleLocaleFallbacks[target] || localeMeta[target].googleLocale, + source: defaultLocale, format: "text", }), }); diff --git a/app/api/version/route.ts b/app/api/version/route.ts index e9df112..38ff008 100644 --- a/app/api/version/route.ts +++ b/app/api/version/route.ts @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { buildMetadata } from "@/lib/build-metadata"; export const runtime = "nodejs"; @@ -8,8 +9,8 @@ function value(name: string) { } export async function GET() { - const commitSha = value("VERCEL_GIT_COMMIT_SHA") || value("GITHUB_SHA"); - const branch = value("VERCEL_GIT_COMMIT_REF") || value("GITHUB_REF_NAME"); + const commitSha = value("VERCEL_GIT_COMMIT_SHA") || value("GITHUB_SHA") || value("NEXT_PUBLIC_MINDREPLY_BUILD_COMMIT_SHA") || buildMetadata.commitSha; + const branch = value("VERCEL_GIT_COMMIT_REF") || value("GITHUB_REF_NAME") || value("NEXT_PUBLIC_MINDREPLY_BUILD_BRANCH") || buildMetadata.branch; return NextResponse.json({ status: "ok", @@ -19,10 +20,11 @@ export async function GET() { commitSha, shortSha: commitSha ? commitSha.slice(0, 12) : null, branch, - environment: value("VERCEL_ENV"), - url: value("VERCEL_URL"), - projectProductionUrl: value("VERCEL_PROJECT_PRODUCTION_URL"), + environment: value("VERCEL_ENV") || value("NEXT_PUBLIC_MINDREPLY_BUILD_ENVIRONMENT") || buildMetadata.environment, + url: value("VERCEL_URL") || value("NEXT_PUBLIC_MINDREPLY_BUILD_URL") || buildMetadata.url, + projectProductionUrl: value("VERCEL_PROJECT_PRODUCTION_URL") || value("NEXT_PUBLIC_MINDREPLY_PROJECT_PRODUCTION_URL") || buildMetadata.projectProductionUrl, region: value("VERCEL_REGION"), + metadataGeneratedAt: buildMetadata.generatedAt, }, }); } diff --git a/app/capabilities/page.tsx b/app/capabilities/page.tsx index ab5e0c7..8a3cf62 100644 --- a/app/capabilities/page.tsx +++ b/app/capabilities/page.tsx @@ -58,10 +58,10 @@ const serviceLanes = [ { name: "Multilingual surface", status: "Active layer", - signal: "11 priority languages", + signal: "Visitor-matched language", icon: Gauge, - copy: "Country and browser signals set the first language gently, with a subtle manual selector for priority English, European, Gulf, Indian, Japanese, Chinese, Ukrainian, and Bulgarian visitors.", - proof: ["IP-country route", "manual selector", "Google Translate fallback", "Bulgarian support", "RTL support"], + copy: "Visitor IP and browser signals set the first language gently, with a subtle manual selector and Google Translate fallback for full-page translation.", + proof: ["IP-country route", "browser-language fallback", "manual selector", "Google Translate fallback", "RTL support"], }, ]; @@ -84,7 +84,7 @@ const proofItems = [ "Public contact routes through MRagent and the contact form.", "The footer keeps the main CTA simple on desktop and phone.", "Language detection is helpful but gentle; visitors can change it without a loud widget.", - "Bulgarian is included in the selector, geo locale, sitemap, metadata, and Google Translate provider.", + "Visitor IP, browser language, and manual selection decide the language assist path.", "Revenue and provider claims are shown only when connected evidence exists.", ]; @@ -102,7 +102,7 @@ export default function CapabilitiesPage() { Package - Try MRagent + Try MindReply Free @@ -124,7 +124,7 @@ export default function CapabilitiesPage() {

First move

-

Try MRagent

+

Try MindReply Free

Paid offer

@@ -132,7 +132,7 @@ export default function CapabilitiesPage() {

Language layer

-

11 priority languages

+

Visitor-matched

diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 2f3505d..3a55369 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -4,7 +4,7 @@ import { CheckCircle2, ClipboardList, LockKeyhole, - Mail, + Mail, MessageCircle, ReceiptText, ShieldCheck, @@ -91,7 +91,7 @@ export default function ContactPage() {

-
+

When to contact

Use this page for buying friction, not vague browsing.

- The clean route is simple: MRagent resolves the pressure when it can. Contact handles package requests, billing, security owner decisions, and anything that needs a human answer. + The clean route is simple: MRagent resolves the pressure when it can. Contact form handles package requests, billing, security owner decisions, and anything that needs a human answer.

@@ -127,7 +127,7 @@ export default function ContactPage() {

Contact form

Website Completion Package, GBP 600.

- The package turns overloaded website messaging, scattered launch notes, or reply pressure into a ranked action queue and send-ready copy. + The package turns overloaded website messaging, scattered launch notes, or reply pressure into a ranked action queue and send-ready copy. The secure contact form posts through /api/package-request.

diff --git a/app/layout.tsx b/app/layout.tsx index 037b0f6..e8757ab 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,15 +1,18 @@ import type { Metadata } from "next"; import { Inter, Playfair_Display } from "next/font/google"; +import Script from "next/script"; import { SpeedInsights } from "@vercel/speed-insights/next"; import GoogleTranslateProvider from "@/components/GoogleTranslateProvider"; import LocaleAssist from "@/components/LocaleAssist"; import SiteFooter from "@/components/SiteFooter"; +import { localeAlternates, localeMeta, supportedLocales } from "@/lib/locales"; import "./globals.css"; -const inter = Inter({ subsets: ["latin"], variable: "--font-inter" }); +const inter = Inter({ subsets: ["latin", "cyrillic"], variable: "--font-inter" }); const playfair = Playfair_Display({ subsets: ["latin"], variable: "--font-playfair" }); const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://www.mind-reply.com"; +const googleTagId = "G-4TME91CJT5"; export const metadata: Metadata = { metadataBase: new URL(siteUrl), @@ -18,22 +21,10 @@ export const metadata: Metadata = { template: "%s | MindReply", }, description: - "MindReply turns website buying friction, client follow-up pressure, and response overload into one clear next move, a ranked action queue, and privacy-safe assisted close for priority UK, India, Gulf, US, German, Japanese, Brazilian, French, Spanish, Chinese, Ukrainian, and Bulgarian readers.", + "MindReply turns website buying friction, client follow-up pressure, and response overload into one clear next move, a ranked action queue, privacy-safe assisted close, and visitor-matched multilingual support using Visitor IP country, browser language, and a manual language selector.", alternates: { canonical: "/", - languages: { - en: "/", - es: "/?lang=es", - fr: "/?lang=fr", - de: "/?lang=de", - pt: "/?lang=pt", - ar: "/?lang=ar", - hi: "/?lang=hi", - ja: "/?lang=ja", - zh: "/?lang=zh", - uk: "/?lang=uk", - bg: "/?lang=bg", - }, + languages: localeAlternates(siteUrl, "/"), }, manifest: "/manifest.webmanifest", robots: { @@ -74,8 +65,10 @@ export const metadata: Metadata = { "China business communication support", "Ukraine founder communication support", "Bulgaria business communication support", - "Bulgarian website completion service", "Bulgarian professional reply support", + "IP aware business communication support", + "visitor matched multilingual support", + "visitor matched multilingual website support", "Arabic executive communication support", "Hindi founder communication support", "German risk aware professional replies", @@ -91,7 +84,9 @@ export const metadata: Metadata = { siteName: "MindReply", type: "website", locale: "en_GB", - alternateLocale: ["hi_IN", "ar_AE", "ar_SA", "en_US", "de_DE", "ja_JP", "pt_BR", "fr_FR", "es_ES", "zh_CN", "uk_UA", "bg_BG"], + alternateLocale: supportedLocales + .map((locale) => localeMeta[locale].ogLocale) + .filter((locale) => locale !== "en_GB"), images: [ { url: "/opengraph-image", @@ -109,10 +104,10 @@ export const metadata: Metadata = { }, other: { "content-language": "en, es, fr, de, pt, ar, hi, ja, zh, uk, bg", - "geo.placename": "United Kingdom, India, United Arab Emirates, Saudi Arabia, United States, Germany, Japan, Brazil, France, Spain, Bulgaria", - "target-market": "GB, IN, AE, SA, US, DE, JP, BR, FR, ES, BG", - "target-market-priority": "UK > India > UAE > Saudi Arabia > US > Germany > Japan > Brazil > France > Spain > Bulgaria", - "localization-priority": "English, Hindi, Arabic, German, Japanese, Portuguese, French, Spanish, Chinese, Ukrainian, Bulgarian", + "geo.placename": "Visitor country and browser language matched by request headers", + "target-market": "IP-aware multilingual business visitors including Bulgaria", + "target-market-priority": "Visitor IP country > browser language > manual language selector", + "localization-priority": "Visitor-matched multilingual support through country signal, browser language, manual selector, and Google Translate fallback", }, }; @@ -120,6 +115,15 @@ export default function RootLayout({ children }: { children: React.ReactNode }) return ( + {children} diff --git a/app/page.tsx b/app/page.tsx index 97fe519..51672c8 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,7 @@ import Link from "next/link"; import { ArrowRight, + BarChart3, Brain, CheckCircle2, ClipboardList, @@ -25,7 +26,7 @@ const packageCtaLabel = packagePaymentUrl ? "Pay GBP 600" : "Checkout or request const packageRouteLabel = packagePaymentUrl ? "Direct payment enabled" : "Checkout and invoice route ready"; const packageRouteCopy = packagePaymentUrl ? "Scope is confirmed first, then the configured payment link is used before delivery." - : "No payment link is required to begin. MindReply confirms scope, collects billing name and billing email, then routes the GBP 600 invoice before delivery."; + : "No payment link is required to begin. MindReply confirms scope, collects billing name and billing email, then routes the GBP 600 invoice before delivery so the invoice-first route works for B2B buyers."; const navItems = [ { label: "Offer", href: "#offer" }, @@ -76,12 +77,12 @@ const toolRows = [ const authoritySignals = [ { - title: "20+ professional lexicons", + title: "Discipline-specific language", copy: "Founder updates, client delivery, legal-sensitive wording, finance pressure, clinical tone, recruiting replies, and executive messages each need different restraint.", icon: FileText, }, { - title: "Behavioral communication read", + title: "Behavioral expression read", copy: "MRagent names the protected feeling, the likely friction, and the next move without turning the answer into a long essay.", icon: HeartHandshake, }, @@ -131,11 +132,42 @@ const upgradeSteps = [ }, { title: "Growth", - copy: "For recurring weekly overload across inboxes, client replies, follow-ups, and small team message queues.", + copy: "For repeated daily overload: customer responses, follow-ups, sales messages, task threads, and small-team communication rhythm.", }, { title: "Pro", - copy: "For high-trust continuity, sensitive professional tone, approved memory, receipt review, and integration lanes when credentials exist.", + copy: "For sensitive client, sales, hiring, legal-adjacent, founder, finance, and reputation-critical communication that needs deeper refinement and control.", + }, +]; + +const firstSessionPath = [ + { + label: "First user action", + copy: "Paste one tense reply, overloaded page section, client follow-up, or objection into MRagent.", + }, + { + label: "First output", + copy: "Get one direct read: hidden friction, next move, confidence, risk, and a narrow receipt marker.", + }, + { + label: "Aha moment", + copy: "The buyer sees the message or page does not need more wording; it needs a cleaner decision path.", + }, + { + label: "Credit trigger", + copy: "Buy credits when several replies need the same quick polish but the website offer is not leaking buyers.", + }, + { + label: "Package trigger", + copy: "Buy the GBP 600 package when the homepage, pricing, contact route, or offer copy needs repair.", + }, + { + label: "Growth trigger", + copy: "Move to Growth when the same overload repeats every week across inbox, clients, or small team work.", + }, + { + label: "Pro trigger", + copy: "Move to Pro when approved memory, receipt review, integration planning, or sensitive continuity is required.", }, ]; @@ -147,6 +179,25 @@ const proofItems = [ "Revenue, deployment, and integration claims stay tied to real sources instead of optimistic wording.", ]; +const dataHandlingProof = [ + { + title: "Raw text stays out of public proof", + copy: "Reports and website copy use receipt markers, route status, and redacted summaries instead of exposing private messages.", + }, + { + title: "Memory requires approval", + copy: "Growth and Pro can describe memory as a controlled lane only after the user approves the context that should persist.", + }, + { + title: "Integrations are consent-gated", + copy: "Slack, email, and workflow connections are not claimed as active until credentials and channel permissions are configured.", + }, + { + title: "Payment path stays inspectable", + copy: "The GBP 600 package uses a fixed-price checkout when configured, with invoice-first fallback when direct payment is not ready.", + }, +]; + const structuredData = { "@context": "https://schema.org", "@type": "SoftwareApplication", @@ -213,7 +264,7 @@ export default function Home() { Products - Try MRagent + Try MindReply Free
@@ -243,7 +294,7 @@ export default function Home() {

+
+
+
+

First-session conversion logic

+

The first session should make the next purchase obvious.

+

+ MindReply does not need a long demo to sell. It needs one useful read, one visible next move, and a clear trigger for credits, the package, Growth, or Pro. +

+
+
+ {firstSessionPath.map((item) => ( +
+

{item.label}

+

{item.copy}

+
+ ))} +
+
+
+
@@ -427,6 +498,25 @@ export default function Home() { ))}
+
+
+
+

Data handling proof

+

Trust is shown through boundaries the buyer can inspect.

+
+

+ These are operational boundaries, not borrowed compliance claims. They make the privacy promise specific without overstating certification. +

+
+
+ {dataHandlingProof.map((item) => ( +
+

{item.title}

+

{item.copy}

+
+ ))} +
+
@@ -437,7 +527,7 @@ export default function Home() {
- Try MRagent + Try MindReply Free {packageCtaLabel} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index 86f280b..d74f7e6 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -83,6 +83,34 @@ const proof = [ "MRagent is the first support route; contact is the fallback when the agent cannot solve the question.", ]; +const buyingTriggers = [ + { + label: "Use the free read", + trigger: "One tense reply, one unclear follow-up, or one page section needs a quick read.", + next: "Paste it into MRagent and use the answer if the next move is obvious.", + }, + { + label: "Buy credits", + trigger: "Several messages need the same quick treatment, but the website offer is not the problem.", + next: "Use credits for repeated reply polish, pressure reads, and next-step wording.", + }, + { + label: "Buy the GBP 600 package", + trigger: "The homepage, pricing path, contact route, or offer copy is still leaking buyers.", + next: "Request invoice or pay when the payment link is configured; the package fixes the buying path.", + }, + { + label: "Upgrade to Growth", + trigger: "The same overload returns every week across inboxes, client replies, or small team work.", + next: "Use Growth when repeated communication pressure costs time every week.", + }, + { + label: "Upgrade to Pro", + trigger: "Sensitive continuity, approved memory, receipt review, or integration planning becomes necessary.", + next: "Use Pro only when the work needs deeper context and stronger operating controls.", + }, +]; + const faqs = [ { q: "What should I buy first?", @@ -177,6 +205,29 @@ export default function PricingPage() {
+
+
+
+

What to buy next

+

The paid path should remove doubt, not add another decision.

+

+ MindReply uses one rule: prove value first, then move the buyer to the smallest paid option that fixes the actual leak. +

+
+
+ {buyingTriggers.map((item) => ( +
+
+

{item.label}

+

{item.trigger}

+
+
{item.next}
+
+ ))} +
+
+
+
diff --git a/app/products/page.tsx b/app/products/page.tsx index 6506dc6..4d61570 100644 --- a/app/products/page.tsx +++ b/app/products/page.tsx @@ -39,7 +39,7 @@ const products = [ "Privacy-safe receipt shape without public raw text", "Upgrade trigger when the issue is bigger than one reply", ], - primary: { label: "Try MRagent", href: "/agent" }, + primary: { label: "Try MindReply Free", href: "/agent" }, secondary: { label: "See more", href: "/agent" }, }, { @@ -72,7 +72,7 @@ const products = [ "Priority support through info@mind-reply.com", "Best when the same overload returns weekly", ], - primary: { label: "Start with MRagent", href: "/agent" }, + primary: { label: "Try MindReply Free", href: "/agent" }, secondary: { label: "See more", href: "/pricing" }, }, { @@ -225,7 +225,7 @@ export default function ProductsPage() {
- Try MRagent + Try MindReply Free Checkout or invoice diff --git a/app/response-overload/page.tsx b/app/response-overload/page.tsx new file mode 100644 index 0000000..08cfe6b --- /dev/null +++ b/app/response-overload/page.tsx @@ -0,0 +1,189 @@ +import Link from "next/link"; +import { + ArrowRight, + CheckCircle2, + ClipboardList, + Gauge, + LockKeyhole, + Mail, + MessageSquareText, + ReceiptText, + ShieldCheck, + Zap, +} from "lucide-react"; + +export const metadata = { + title: "Response Overload Rescue | MindReply", + description: + "Turn overloaded replies, client follow-ups, objections, and urgent message queues into one clear next move. Start free with MRagent, then buy credits, the GBP 600 Website Completion Package, Growth, or Pro only when the trigger is clear.", + alternates: { + canonical: "https://www.mind-reply.com/response-overload", + }, +}; + +const packagePaymentUrl = process.env.NEXT_PUBLIC_WEBSITE_COMPLETION_PACKAGE_PAYMENT_URL || ""; +const packageHref = packagePaymentUrl || "/contact?intent=website-completion"; +const packageLabel = packagePaymentUrl ? "Pay for the GBP 600 package" : "Request GBP 600 invoice"; + +const painSignals = [ + "Client replies are taking too long because every answer needs judgement.", + "The team has urgent messages, objections, and follow-ups but no clear order.", + "The website or offer asks people to trust you before the buying path is clear.", + "Sensitive wording needs restraint, not generic AI polish.", +]; + +const conversionPath = [ + { + title: "Free MRagent read", + copy: "Paste one stuck reply, follow-up, objection, or page section. Get one direct read and one safer move.", + icon: MessageSquareText, + }, + { + title: "Credits", + copy: "Use credits when several messages need quick pressure reads and send-ready polish.", + icon: Zap, + }, + { + title: "GBP 600 package", + copy: "Use the Website Completion Package when the homepage, offer, pricing, or contact route is leaking buyers.", + icon: ReceiptText, + }, + { + title: "Growth or Pro", + copy: "Move to Growth when overload repeats weekly. Move to Pro when approved memory, receipt review, or integration planning is needed.", + icon: ShieldCheck, + }, +]; + +const trustRows = [ + "Raw private text is not used as public proof.", + "Receipts use narrow markers and redacted summaries.", + "Memory and integrations require approval before being claimed as active.", + "Fixed-price and invoice paths stay visible before delivery starts.", +]; + +export default function ResponseOverloadPage() { + return ( +
+
+
+ + M + MindReply + +
+ + Pricing + + + Try free + +
+
+
+ +
+ +
+ +
+
+
+

When this page should convert

+

The buyer has pressure, not time for a platform tour.

+

+ The offer is intentionally narrow: show useful judgement first, then point to the smallest paid option that fixes the real leak. +

+
+
+ {painSignals.map((signal) => ( +
+ + {signal} +
+ ))} +
+
+
+ +
+
+
+
+

Paid path

+

Every purchase needs a trigger.

+
+

+ This keeps the page ad-ready: the user sees what to do first, what to buy next, and why the higher plan is not being pushed too early. +

+
+
+ {conversionPath.map((item) => { + const Icon = item.icon; + return ( +
+ + + +

{item.title}

+

{item.copy}

+
+ ); + })} +
+
+
+ +
+
+
+
+ +

First output

+
+

One synthesis. One next move. One receipt marker.

+

+ The first output should prove that MindReply understands the pressure without asking for setup, integrations, or a long onboarding call. +

+
+
+
+ +

Trust boundary

+
+
+ {trustRows.map((row) => ( +
+ + {row} +
+ ))} +
+
+
+
+
+ ); +} diff --git a/app/robots.ts b/app/robots.ts index a638444..861b7d3 100644 --- a/app/robots.ts +++ b/app/robots.ts @@ -16,6 +16,7 @@ export default function robots(): MetadataRoute.Robots { "/pricing", "/contact", "/capabilities", + "/trust", "/privacy", "/sitemap.xml", "/manifest.webmanifest", diff --git a/app/sitemap.ts b/app/sitemap.ts index 055d2fa..bb6ff6d 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,40 +1,42 @@ import type { MetadataRoute } from "next"; +import { defaultLocale, localeAlternates, localizedPath, supportedLocales, type LocaleCode } from "@/lib/locales"; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://www.mind-reply.com"; -const languageParams = ["es", "fr", "de", "pt", "ar", "hi", "ja", "zh", "uk", "bg"]; - const routes = [ { path: "/", priority: 1, changeFrequency: "daily" as const, localized: true }, { path: "/agent", priority: 0.95, changeFrequency: "daily" as const, localized: true }, + { path: "/response-overload", priority: 0.945, changeFrequency: "daily" as const, localized: true }, { path: "/products", priority: 0.94, changeFrequency: "daily" as const, localized: true }, { path: "/website-completion-package", priority: 0.92, changeFrequency: "daily" as const, localized: true }, { path: "/checkout", priority: 0.9, changeFrequency: "weekly" as const, localized: true }, { path: "/pricing", priority: 0.88, changeFrequency: "weekly" as const, localized: true }, + { path: "/trust", priority: 0.82, changeFrequency: "weekly" as const, localized: true }, { path: "/capabilities", priority: 0.75, changeFrequency: "weekly" as const, localized: true }, { path: "/contact", priority: 0.55, changeFrequency: "monthly" as const, localized: true }, - { path: "/privacy", priority: 0.5, changeFrequency: "monthly" as const, localized: false }, + { path: "/privacy", priority: 0.5, changeFrequency: "monthly" as const, localized: true }, ]; -function localeAlternates(path: string) { - return { - en: `${siteUrl}${path}`, - ...Object.fromEntries(languageParams.map((locale) => [locale, `${siteUrl}${path}?lang=${locale}`])), - }; +function languageParams(pathname: string) { + const path = pathname.replace(/\/$/, "") || "/"; + return Object.fromEntries(supportedLocales.map((locale) => [locale, `${siteUrl}${path}?lang=${locale}`])); } export default function sitemap(): MetadataRoute.Sitemap { const lastModified = new Date(); - return routes.map((route) => ({ - url: `${siteUrl}${route.path}`, - lastModified, - changeFrequency: route.changeFrequency, - priority: route.priority, - alternates: route.localized - ? { - languages: localeAlternates(route.path), - } - : undefined, - })); + return routes.flatMap((route) => { + const alternates = route.localized + ? { languages: { ...localeAlternates(siteUrl, route.path), ...languageParams(route.path) } } + : undefined; + const locales: readonly LocaleCode[] = route.localized ? supportedLocales : [defaultLocale]; + + return locales.map((locale) => ({ + url: `${siteUrl}${localizedPath(route.path, locale)}`, + lastModified, + changeFrequency: route.changeFrequency, + priority: locale === defaultLocale ? route.priority : Math.max(route.priority - 0.03, 0.45), + alternates, + })); + }); } diff --git a/app/trust/page.tsx b/app/trust/page.tsx new file mode 100644 index 0000000..b9a7edc --- /dev/null +++ b/app/trust/page.tsx @@ -0,0 +1,151 @@ +import Link from "next/link"; +import { ArrowRight, CheckCircle2, LockKeyhole, Mail, ReceiptText, ShieldCheck } from "lucide-react"; + +export const metadata = { + title: "Trust and Data Handling | MindReply", + description: + "MindReply trust proof: privacy-safe receipts, consent-gated memory, public support route, invoice-first buying path, and restrained claims for sensitive professional communication.", + alternates: { + canonical: "https://www.mind-reply.com/trust", + }, +}; + +const proofRows = [ + { + title: "Raw private text is not public proof", + copy: "Reports, website language, and delivery evidence use receipt markers, route status, hashes, and redacted summaries instead of publishing private messages.", + icon: LockKeyhole, + }, + { + title: "Receipts are narrow on purpose", + copy: "The receipt shape is limited to source, timestamp, risk, confidence, action kind, input hash, and rawContentRedacted status.", + icon: ReceiptText, + }, + { + title: "Memory requires explicit approval", + copy: "Growth and Pro can describe memory only as an approved lane. Sensitive context is not claimed as persistent until the user chooses what should carry forward.", + icon: ShieldCheck, + }, + { + title: "Human handoff uses the public route", + copy: "Public support and package requests use info@mind-reply.com, the contact form, or the checkout/invoice route. Personal inboxes stay out of public pages.", + icon: Mail, + }, +]; + +const claimRules = [ + "No customer count, revenue, staff, compliance badge, payment status, or integration status is stated without evidence.", + "Slack, email, memory, MCP, and payment routes are described as active only when credentials, permissions, and workflow proof exist.", + "The Website Completion Package keeps scope, price, and invoice/payment route visible before private context is reviewed.", + "Security and owner decisions stay in private, redacted reports; public pages explain boundaries without exposing sensitive data.", +]; + +const publicSurfaces = [ + { label: "Privacy page", href: "/privacy", copy: "Explains raw-input restraint, receipt shape, review, and derived memory." }, + { label: "Contact route", href: "/contact", copy: "Handles human follow-up, package requests, billing details, and security owner routing." }, + { label: "Checkout route", href: "/checkout?package=website-completion", copy: "Shows GBP 600 fixed price, direct-payment readiness, and invoice fallback." }, + { label: "Package page", href: "/website-completion-package", copy: "Shows deliverables, buyer proof checklist, and invoice-first paymentPath receipt." }, +]; + +export default function TrustPage() { + return ( +
+
+
+ + M + MindReply + + + Try MindReply Free + +
+
+ +
+
+
+

+ Trust proof +

+

Private by design means the boundary is visible.

+
+

+ MindReply is used for sensitive professional communication, so the trust claim has to be inspectable: what is stored, what is redacted, when memory is allowed, and which routes are actually configured. +

+
+
+ +
+
+ {proofRows.map((row) => { + const Icon = row.icon; + return ( +
+ + + +

{row.title}

+

{row.copy}

+
+ ); + })} +
+
+ +
+
+
+

Claim discipline

+

No borrowed trust badges. No invented proof.

+

+ Testimonials and compliance badges are useful only when they are real. Until then, MindReply shows the operational boundary and keeps unsupported claims out of public copy. +

+
+
+ {claimRules.map((rule) => ( +
+ + {rule} +
+ ))} +
+
+
+ +
+
+
+

Inspectable surfaces

+

The proof is spread across public routes buyers can check.

+
+
+ {publicSurfaces.map((surface) => ( + +

{surface.label}

+

{surface.copy}

+ + ))} +
+
+
+ +
+
+
+

Next step

+

Use the free read first. Use the GBP 600 package when the trust and buying path need completion.

+
+
+ + Try MindReply Free + + + Website Completion Package + +
+
+
+
+ ); +} diff --git a/app/website-completion-package/page.tsx b/app/website-completion-package/page.tsx index 7497d22..db11b3e 100644 --- a/app/website-completion-package/page.tsx +++ b/app/website-completion-package/page.tsx @@ -80,10 +80,59 @@ const trust = [ "Revenue and conversion claims stay tied to verified sources only.", ]; -const assets = [ - "LinkedIn opener: Your site already has the product. The leak is the buying path. I can turn the current page into a ranked action queue and send-ready close copy.", - "Cold email opener: I noticed the offer is doing more explaining than closing. MindReply can compress the next buying step into a Website Completion Package.", - "Follow-up line: The goal is not a redesign. It is one clear path from pressure to purchase, with a receipt for what changed.", +const buyerProofChecklist = [ + "The buyer can inspect the GBP 600 price before sending private context.", + "The buyer can choose invoice-first when a direct payment link is not configured.", + "The buyer can see what is processed: messaging, offer, trust, and path to pay.", + "The buyer can see what is returned: ranked queue, send-ready copy, and receipt.", + "The buyer can see what is protected: raw sensitive text stays out of public proof.", +]; + +const outboundDms = [ + { label: "DM 1", copy: "Your offer is close, but the page is asking the buyer to think too hard. I can turn it into a ranked action queue and one send-ready close path." }, + { label: "DM 2", copy: "I would not start with a redesign. I would fix the exact point where a serious buyer loses the next step." }, + { label: "DM 3", copy: "Your product looks useful. The buying path needs sharper proof, price clarity, and one calmer route to act." }, + { label: "DM 4", copy: "If the page is getting attention but not decisions, MindReply can compress the offer into the next paid move without adding noise." }, + { label: "DM 5", copy: "Send the page or reply that is slowing the sale. I will return the friction list, the next wording, and what should be protected." }, +]; + +const coldEmails = [ + { + subject: "Your page is explaining more than it is closing", + body: "I noticed the offer has enough substance, but the next buying step is not doing enough work. MindReply can turn the current page into a Website Completion Package: ranked friction, send-ready copy, trust proof, and a clear checkout or invoice route.", + }, + { + subject: "One focused rescue pass for the buying path", + body: "This is not a redesign pitch. It is a GBP 600 pass to find where buyers hesitate, rewrite the pressure points, and return a privacy-safe receipt showing what changed and what still needs a decision.", + }, + { + subject: "A faster way to make the offer inspectable", + body: "High-trust buyers need price, proof, scope, and privacy boundaries before they share context. MindReply packages those pieces into one clear path so the page can move from interest to action.", + }, +]; + +const followUps = [ + { label: "Follow-up 1", copy: "Worth doing only if the page is already getting qualified attention. The package is meant to remove buying friction, not decorate the site." }, + { label: "Follow-up 2", copy: "The smallest useful next step is to send the page, offer block, or reply path that feels stuck. I will return the ranked friction and the close-ready move." }, +]; + +const objectionResponses = [ + { + objection: "We do not need a redesign.", + response: "Correct. The package is not sold as a redesign. It fixes clarity, proof, pricing route, and next-step copy where the buyer hesitates.", + }, + { + objection: "We are not ready to share sensitive details.", + response: "Start with the public page or a redacted sample. Raw private input is not used as public proof, and the receipt can stay privacy-safe.", + }, + { + objection: "Why pay before seeing the work?", + response: "Use MRagent first for the free read. Buy the package only when the first output proves the friction is real and the fixed path is worth completing.", + }, + { + objection: "Is this for startups or service businesses?", + response: "Use it when a buyer must understand scope, proof, price, and next action quickly. That applies to operators, consultants, agencies, founders, and client-facing teams.", + }, ]; export default function WebsiteCompletionPackagePage() { @@ -200,6 +249,26 @@ export default function WebsiteCompletionPackagePage() {
+
+
+
+

Buyer proof checklist

+

The package must feel inspectable before payment.

+

+ This is the credibility layer for the GBP 600 offer: clear price, clear route, clear output, and a clean privacy boundary. +

+
+
+ {buyerProofChecklist.map((item) => ( +
+ + {item} +
+ ))} +
+
+
+
@@ -238,15 +307,22 @@ export default function WebsiteCompletionPackagePage() {
-
+

Assisted-close assets

+

Use these when a buyer is interested but the next move is soft.

+

+ The outreach stays direct: name the leak, offer one focused rescue, and keep sensitive proof private until the buyer chooses scope. +

- {assets.map((asset) => ( -

{asset}

+ {outboundDms.map((asset) => ( +

+ {asset.label}: + {asset.copy} +

))}
@@ -262,6 +338,54 @@ export default function WebsiteCompletionPackagePage() {
+ +
+
+
+

Cold email set

+

Three emails for buyers who need proof before a call.

+

+ Each email sells the smallest paid move: fix the buying path, return proof, and keep claims tied to verified sources. +

+
+
+ {coldEmails.map((email) => ( +
+

Subject

+

{email.subject}

+

{email.body}

+
+ ))} +
+
+
+ +
+
+
+

Two follow-ups

+
+ {followUps.map((item) => ( +

+ {item.label}: + {item.copy} +

+ ))} +
+
+
+

Objection handling

+
+ {objectionResponses.map((item) => ( +
+

{item.objection}

+

{item.response}

+
+ ))} +
+
+
+
); } diff --git a/archive/branch-cleanup-2026-06-09/MANIFEST.md b/archive/branch-cleanup-2026-06-09/MANIFEST.md new file mode 100644 index 0000000..4f58c5d --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/MANIFEST.md @@ -0,0 +1,79 @@ +# MindReply Branch Cleanup Archive + +Generated: 2026-06-09 + +This archive preserves selected useful files from cleanup branches before deleting stale GitHub branches. It is stored on `mind-reply`, which is the storage/reference branch. It is not part of production `main`. + +## Branches Reviewed + +- `codespace-super-computing-machine-wv7jrq4q7xjj3vq45` at `705694bd91682fb474e496e2f96f65c62cbf81a4` +- `codex/executive-nervous-system` at `8f8a62592ce15880c769e6b305082f9ed6eac3a7` +- `codex/executive-nervous-system-build` at `b31544b8133f6c1a2518c94345e1e2a141131e02` +- `codex/executive-nervous-system-main-sync` at `fae379006f0d771fbe4780b7782ec57272768c02` +- `codex/executive-nervous-system-rebrand` at `b9800cf331f9fef5afe62365a83659204267069d` +- `codex/full-frontend-platform-pack` at `485c37eac5471c44b53c158e90c01a1ae250188a` +- `codex/hourly-owner-report-current` at `6b4812468f12a8afee0ab38e8889bf1cc75ca035` +- `codex/mindreply-moa-controller` at `54f4e1693ef403a86ff6822555c4f849cab46325` +- `codex/mindreply-moa-main` at `7f74c8d766eb36768d25c2526f5e34560708ba58` +- `codex/mindreply-moa-production-minimal` at `e11090111b37679152bf35162593a219898fe86c` +- `codex/mragent-decision-chat` at `08f0ff3a1b4da034af8de4b5bd966349bc6da5f6` +- `codex/security-revenue-frontend-current` at `8a26cbc967aa57f5d3eb6b19d9e2ff15227d1071` + +## Archived Files + +- `mind-reply` / `reports/2026-06-09-vercel-quota-guard-followup.md` -> `archive/branch-cleanup-2026-06-09/mind-reply/reports/2026-06-09-vercel-quota-guard-followup.md` +- `codex/mindreply-moa-controller` / `components/MOAConsole.tsx` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/components/MOAConsole.tsx` +- `codex/mindreply-moa-controller` / `lib/moa.ts` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/lib/moa.ts` +- `codex/mindreply-moa-controller` / `tests/moa.test.ts` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/tests/moa.test.ts` +- `codex/mindreply-moa-controller` / `docs/MINDREPLY_MULTI_AGENT_BLUEPRINT.md` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/docs/MINDREPLY_MULTI_AGENT_BLUEPRINT.md` +- `codex/mindreply-moa-controller` / `app/api/agent/orchestrate/route.ts` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agent/orchestrate/route.ts` +- `codex/mindreply-moa-controller` / `app/api/agents/route.ts` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agents/route.ts` +- `codex/mindreply-moa-controller` / `app/agents/page.tsx` -> `archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/agents/page.tsx` +- `codex/mragent-decision-chat` / `app/agent/page.tsx` -> `archive/branch-cleanup-2026-06-09/codex_mragent-decision-chat/app/agent/page.tsx` +- `codex/mragent-decision-chat` / `app/api/agent/route.ts` -> `archive/branch-cleanup-2026-06-09/codex_mragent-decision-chat/app/api/agent/route.ts` +- `codex/mragent-decision-chat` / `components/MRAgentChat.tsx` -> `archive/branch-cleanup-2026-06-09/codex_mragent-decision-chat/components/MRAgentChat.tsx` +- `codex/mragent-decision-chat` / `components/ai-elements/message.tsx` -> `archive/branch-cleanup-2026-06-09/codex_mragent-decision-chat/components/ai-elements/message.tsx` +- `codex/mragent-decision-chat` / `scripts/verify-decision-layer.ts` -> `archive/branch-cleanup-2026-06-09/codex_mragent-decision-chat/scripts/verify-decision-layer.ts` +- `codex/full-frontend-platform-pack` / `lib/request-safety.ts` -> `archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/lib/request-safety.ts` +- `codex/full-frontend-platform-pack` / `docs/security_owner_decision_report.md` -> `archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/security_owner_decision_report.md` +- `codex/full-frontend-platform-pack` / `docs/front_end_operating_pack.md` -> `archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/front_end_operating_pack.md` +- `codex/security-revenue-frontend-current` / `lib/request-safety.ts` -> `archive/branch-cleanup-2026-06-09/codex_security-revenue-frontend-current/lib/request-safety.ts` +- `codex/security-revenue-frontend-current` / `docs/hourly_owner_goal_prompt.md` -> `archive/branch-cleanup-2026-06-09/codex_security-revenue-frontend-current/docs/hourly_owner_goal_prompt.md` +- `codex/security-revenue-frontend-current` / `docs/security_owner_decision_report.md` -> `archive/branch-cleanup-2026-06-09/codex_security-revenue-frontend-current/docs/security_owner_decision_report.md` +- `codex/security-revenue-frontend-current` / `scripts/hourly-owner-report-lib.ts` -> `archive/branch-cleanup-2026-06-09/codex_security-revenue-frontend-current/scripts/hourly-owner-report-lib.ts` +- `codex/security-revenue-frontend-current` / `scripts/hourly-owner-report-send.ts` -> `archive/branch-cleanup-2026-06-09/codex_security-revenue-frontend-current/scripts/hourly-owner-report-send.ts` +- `codex/executive-nervous-system-build` / `playbooks/schema.json` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/playbooks/schema.json` +- `codex/executive-nervous-system-build` / `src/backend/README.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/README.md` +- `codex/executive-nervous-system-build` / `src/chatgpt-app/README.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/README.md` +- `codex/executive-nervous-system-build` / `src/chatgpt-app/mragent-tools.json` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/mragent-tools.json` +- `codex/executive-nervous-system-build` / `src/agents/prompts.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/agents/prompts.md` +- `codex/executive-nervous-system-build` / `src/backend/audit_log.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/audit_log.py` +- `codex/executive-nervous-system-build` / `src/backend/followup_engine.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/followup_engine.py` +- `codex/executive-nervous-system-build` / `src/backend/memory_store.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/memory_store.py` +- `codex/executive-nervous-system-build` / `src/backend/playbook_interpreter.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/playbook_interpreter.py` +- `codex/executive-nervous-system-build` / `src/backend/reply_engine.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/reply_engine.py` +- `codex/executive-nervous-system-build` / `src/backend/risk_engine.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/risk_engine.py` +- `codex/executive-nervous-system-build` / `src/backend/triage_engine.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/triage_engine.py` +- `codex/executive-nervous-system-build` / `src/backend/tests/test_decision_layer.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/tests/test_decision_layer.py` +- `codex/executive-nervous-system-build` / `src/edge/extension/background.js` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/background.js` +- `codex/executive-nervous-system-build` / `src/edge/extension/content.js` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/content.js` +- `codex/executive-nervous-system-build` / `src/edge/extension/styles.css` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/styles.css` +- `codex/executive-nervous-system-build` / `src/integrations/calendar_connector.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/calendar_connector.py` +- `codex/executive-nervous-system-build` / `src/integrations/gmail_connector.py` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/gmail_connector.py` +- `codex/executive-nervous-system-rebrand` / `docs/advanced_team_blueprint.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/advanced_team_blueprint.md` +- `codex/executive-nervous-system-rebrand` / `docs/observability_contract.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/observability_contract.md` +- `codex/executive-nervous-system-rebrand` / `docs/owner_security_blueprint.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/owner_security_blueprint.md` +- `codex/executive-nervous-system-rebrand` / `docs/hourly_owner_goal_prompt.md` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/hourly_owner_goal_prompt.md` +- `codex/executive-nervous-system-rebrand` / `app/api/owner/decision/route.ts` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/decision/route.ts` +- `codex/executive-nervous-system-rebrand` / `app/api/owner/export/route.ts` -> `archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/export/route.ts` + +## Excluded On Purpose + +- `.env`, `.env.local`, OAuth tokens, webhook URLs, provider secrets, and secret-like files. +- `tokens.yaml` from PR #15 / codespace branch. +- generated build output, `.next`, `*.tsbuildinfo`, `node_modules`, logs, and bulk generated HTML/JS not needed as source-of-truth. +- broad deleted-file snapshots from old branches; Git history remains available until branch deletion, and selected useful sources are archived here. + +## Cleanup Target + +After this archive is pushed, keep only `main` and `mind-reply` branches. diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/playbooks/schema.json b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/playbooks/schema.json new file mode 100644 index 0000000..006408c --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/playbooks/schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "MindReply Playbook", + "type": "object", + "required": ["playbook_id", "title", "description", "version", "triggers", "decision_tree", "actions", "verification", "audit"], + "properties": { + "playbook_id": { "type": "string", "minLength": 1 }, + "title": { "type": "string", "minLength": 1 }, + "description": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+$" }, + "triggers": { + "type": "array", + "items": { + "type": "object", + "required": ["type", "pattern"], + "properties": { + "type": { "enum": ["email", "calendar", "file", "webhook", "manual"] }, + "pattern": { "type": "string", "minLength": 1 } + } + } + }, + "decision_tree": { + "type": "object", + "required": ["nodes"], + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "condition", "action", "next"], + "properties": { + "id": { "type": "string" }, + "condition": { "type": "string" }, + "action": { "enum": ["reply", "schedule", "resolve", "escalate"] }, + "next": { "type": "array", "items": { "type": "string" } } + } + } + } + } + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "template_id", "params"], + "properties": { + "id": { "type": "string" }, + "type": { "enum": ["email_template", "api_call", "task_create", "calendar_event", "noop"] }, + "template_id": { "type": "string" }, + "params": { "type": "object" } + } + } + }, + "verification": { + "type": "object", + "required": ["required", "roles"], + "properties": { + "required": { "type": "boolean" }, + "roles": { "type": "array", "items": { "type": "string" } } + } + }, + "audit": { + "type": "object", + "required": ["exportable", "signed"], + "properties": { + "exportable": { "type": "boolean" }, + "signed": { "type": "boolean" } + } + } + } +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/agents/prompts.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/agents/prompts.md new file mode 100644 index 0000000..f4e28a0 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/agents/prompts.md @@ -0,0 +1,111 @@ +# MindReply Agent Prompts + +All agents work inside the Executive Nervous System category. Every output must produce one synthesis and one recommended action. No agent may create alternate paths or expose internal reasoning to the user. + +## Triage Agent + +Role: read the intake and classify importance, urgency, source, and required action. + +Allowed input: + +```json +{ + "input": "string", + "source": "manual | gmail | calendar | extension", + "device_privacy_flag": false, + "playbook_id": "optional string" +} +``` + +Required output: + +```json +{ + "importance": 0, + "urgency": 0, + "required_action": "reply | schedule | resolve | escalate", + "synthesis": "string", + "confidence": 0.82, + "playbook_id": "string" +} +``` + +Escalation behavior: if the intake contains legal, safety, regulatory, clinical, financial-control, or relationship harm signals, return `required_action: "escalate"`. + +## Reply Agent + +Role: prepare one calm reply when the required action is `reply` and risk allows movement. + +Allowed input: + +```json +{ + "input": "string", + "triage": { + "required_action": "reply", + "synthesis": "string", + "playbook_id": "string" + } +} +``` + +Required output: + +```json +{ + "synthesis": "string", + "recommended_action": { + "kind": "reply", + "label": "Send the prepared reply", + "payload": { + "draft": "string" + } + } +} +``` + +Escalation behavior: if risk is high, return no draft and hand the item to the Risk Agent. + +## Follow-Up Agent + +Role: create one timed follow-up when the required action is `schedule`. + +Required output: + +```json +{ + "next_check_timestamp": "ISO string", + "action": "remind | escalate", + "rationale": "string", + "synthesis": "string", + "recommended_action": { + "kind": "schedule | escalate", + "label": "string", + "payload": { + "title": "string", + "starts_at": "ISO string", + "duration_minutes": 15 + } + } +} +``` + +## Risk Agent + +Role: check whether action should be held before movement. + +Required output: + +```json +{ + "risk_level": "low | medium | high", + "level": "low | medium | high", + "reason": "string", + "escalate": true, + "required_verification_roles": ["owner"] +} +``` + +## Shared Rule + +If the input carries legal, safety, regulatory, clinical, financial-control, or relationship risk, the recommended action becomes `escalate`. diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/README.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/README.md new file mode 100644 index 0000000..51e3d0e --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/README.md @@ -0,0 +1,33 @@ +# MindReply Backend Layer + +This directory contains the private Executive Nervous System mechanics. + +## Layers + +- Intake Layer: `triage_engine.py` classifies importance, urgency, source, and required action. +- Action Layer: `reply_engine.py` and `followup_engine.py` produce exactly one next movement. +- Memory Layer: `memory_store.py` stores derived preferences only. Raw input is excluded by default. + +## Four Agents + +- Triage Agent: assigns numeric importance, numeric urgency, required action, and playbook id. +- Reply Agent: creates one calm reply when movement is safe. +- Follow-Up Agent: creates one timed follow-up or escalation event. +- Risk Agent: blocks unsafe movement and requires review where needed. + +## Receipts + +`audit_log.py` writes hash-based receipts with playbook id, version, action, redaction level, and signatures. It must never write raw private text unless a later protected workflow captures explicit consent and retention settings. + +## Playbooks + +`playbook_interpreter.py` loads signed/versioned seed playbooks from `playbooks/seed`. Each playbook must validate against `playbooks/schema.json`. + +## Verification + +Run: + +```bash +python -m unittest discover src +npm run decision:verify +``` diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/audit_log.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/audit_log.py new file mode 100644 index 0000000..0fe47a6 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/audit_log.py @@ -0,0 +1,64 @@ +"""Purpose: append-only privacy-safe audit receipts for executed actions. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +import hashlib +import hmac +import json +import os +import uuid +from datetime import datetime, timezone +from pathlib import Path + + +class AuditLog: + def __init__(self, path: str = "audit.jsonl", signing_key: str | None = None) -> None: + self.path = Path(path) + self.signing_key = signing_key or os.environ.get("MINDREPLY_AUDIT_SIGNING_KEY", "local-dev-signing-key") + + def record( + self, + raw_input: str, + source: str, + action_kind: str, + playbook_id: str = "clear-next-move", + playbook_version: str = "1.0", + synthesis: str = "Decision recorded.", + actor: str = "server", + redaction_level: str = "full", + rationale: str = "One recommended action was produced.", + ) -> dict: + timestamp = datetime.now(timezone.utc).isoformat() + input_hash = hashlib.sha256(raw_input.encode("utf-8")).hexdigest() + receipt = { + "receipt_id": str(uuid.uuid4()), + "timestamp": timestamp, + "playbook_id": playbook_id, + "playbook_version": playbook_version, + "synthesis": synthesis, + "action": action_kind, + "actor": actor, + "input_hash": input_hash, + "source": source, + "redaction_level": redaction_level, + "rationale": rationale, + } + receipt["signatures"] = [{"key_id": "local-dev", "sig": self._sign(receipt)}] + self.path.parent.mkdir(parents=True, exist_ok=True) + with self.path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(receipt, sort_keys=True) + "\n") + return { + "id": receipt["receipt_id"], + "receipt_id": receipt["receipt_id"], + "timestamp": timestamp, + "source": source, + "playbook_id": playbook_id, + "playbook_version": playbook_version, + "signatures": receipt["signatures"], + } + + def _sign(self, receipt: dict) -> str: + payload = json.dumps({key: value for key, value in receipt.items() if key != "signatures"}, sort_keys=True).encode("utf-8") + return hmac.new(self.signing_key.encode("utf-8"), payload, hashlib.sha256).hexdigest() diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/followup_engine.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/followup_engine.py new file mode 100644 index 0000000..321f297 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/followup_engine.py @@ -0,0 +1,28 @@ +"""Purpose: Action Layer follow-up scheduling for one next check. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +from datetime import datetime, timedelta, timezone + + +class FollowUpEngine: + def schedule(self, triage: dict, minutes_from_now: int = 60, user_preferences: dict | None = None) -> dict: + starts_at = datetime.now(timezone.utc) + timedelta(minutes=minutes_from_now) + action = "escalate" if triage.get("required_action") == "escalate" else "remind" + return { + "next_check_timestamp": starts_at.isoformat(), + "action": action, + "rationale": "One quiet follow-up keeps the item contained.", + "synthesis": triage.get("synthesis") or "The matter needs a timed follow-up.", + "recommended_action": { + "kind": "schedule" if action == "remind" else "escalate", + "label": "Set the follow-up" if action == "remind" else "Hold and review", + "payload": { + "title": "MindReply follow-up", + "starts_at": starts_at.isoformat(), + "duration_minutes": 15, + }, + }, + } diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/memory_store.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/memory_store.py new file mode 100644 index 0000000..ce07416 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/memory_store.py @@ -0,0 +1,29 @@ +"""Purpose: Memory Layer stores derived preferences without raw input. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + + +class MemoryStore: + def __init__(self) -> None: + self._records: dict[str, list[dict]] = {} + + def update(self, user_id: str, raw_input: str, decision: dict) -> dict: + action = decision.get("recommended_action", {}).get("kind", decision.get("required_action", "resolve")) + risk = decision.get("risk", {}).get("level", decision.get("risk_level", "low")) + derived = { + "preferred_action": action, + "tone": "calm", + "follow_up_bias": "contained", + "risk_bias": "review" if risk in {"medium", "high"} else "normal", + "signal_size": "short" if len(raw_input) < 240 else "long", + } + self._records.setdefault(user_id, []).append(derived) + return { + "applied": True, + "summary": "Decision memory adjusted quietly.", + } + + def export(self, user_id: str) -> list[dict]: + return list(self._records.get(user_id, [])) diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/playbook_interpreter.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/playbook_interpreter.py new file mode 100644 index 0000000..e72088b --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/playbook_interpreter.py @@ -0,0 +1,64 @@ +"""Purpose: select one signed/versioned playbook for an intake. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +import json +from pathlib import Path + + +ACTIONS = {"reply", "schedule", "resolve", "escalate"} +REQUIRED_KEYS = {"playbook_id", "title", "description", "version", "triggers", "decision_tree", "actions", "verification", "audit"} + + +class PlaybookInterpreter: + def __init__(self, seed_dir: str | None = None) -> None: + root = Path(__file__).resolve().parents[2] + self.seed_dir = Path(seed_dir) if seed_dir else root / "playbooks" / "seed" + self.playbooks = self._load_playbooks() + + def select(self, raw_input: str) -> dict: + lower = raw_input.lower() + for playbook in self.playbooks: + for trigger in playbook.get("triggers", []): + pattern = str(trigger.get("pattern", "")).lower() + if pattern and pattern in lower: + return self._result(playbook) + return { + "playbook_id": "clear-next-move", + "title": "Clear next move", + "recommended_action": "resolve", + "version": "1.0", + } + + def validate(self, playbook: dict) -> None: + missing = REQUIRED_KEYS - set(playbook.keys()) + if missing: + raise ValueError(f"Playbook missing keys: {sorted(missing)}") + for node in playbook.get("decision_tree", {}).get("nodes", []): + action = node.get("action") + if action not in ACTIONS: + raise ValueError(f"Invalid action: {action}") + if not isinstance(playbook.get("audit", {}).get("signed"), bool): + raise ValueError("Playbook audit.signed must be boolean") + + def _load_playbooks(self) -> list[dict]: + if not self.seed_dir.exists(): + return [] + playbooks: list[dict] = [] + for path in sorted(self.seed_dir.glob("*.json")): + with path.open("r", encoding="utf-8") as handle: + playbook = json.load(handle) + self.validate(playbook) + playbooks.append(playbook) + return playbooks + + def _result(self, playbook: dict) -> dict: + first_node = playbook.get("decision_tree", {}).get("nodes", [{}])[0] + return { + "playbook_id": playbook["playbook_id"], + "title": playbook["title"], + "recommended_action": first_node.get("action", "resolve"), + "version": playbook["version"], + } diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/reply_engine.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/reply_engine.py new file mode 100644 index 0000000..f582afa --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/reply_engine.py @@ -0,0 +1,36 @@ +"""Purpose: Action Layer reply drafting for one calm response. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + + +class ReplyEngine: + def draft(self, raw_input: str, triage: dict, user_profile: dict | None = None) -> dict: + synthesis = triage.get("synthesis") or "The message needs a calm response." + action = triage.get("required_action", "reply") + if action != "reply": + return { + "synthesis": synthesis, + "recommended_action": { + "kind": action, + "label": "Proceed when ready", + "payload": {"rationale": synthesis}, + }, + } + + tone = (user_profile or {}).get("tone", "calm") + draft = self._draft_for_tone(tone) + return { + "synthesis": synthesis, + "recommended_action": { + "kind": "reply", + "label": "Send the prepared reply", + "payload": {"draft": draft}, + }, + } + + def _draft_for_tone(self, tone: str) -> str: + if tone == "formal": + return "Thank you for being direct. I understand the concern and want to keep the next step clear. Let us confirm the decision point and agree the timing." + return "Thank you for being direct. I understand the concern. The next step is to confirm the decision point, protect the relationship, and agree the timing." diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/risk_engine.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/risk_engine.py new file mode 100644 index 0000000..82fb650 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/risk_engine.py @@ -0,0 +1,49 @@ +"""Purpose: Risk Layer validation before movement. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + + +class RiskEngine: + HIGH_RISK_TERMS = { + "threat", + "force", + "blackmail", + "harass", + "illegal", + "self-harm", + "suicide", + "regulator", + "lawsuit", + "breach", + "wire fraud", + } + + MEDIUM_RISK_TERMS = {"complaint", "refund", "termination", "medical", "legal", "fire", "contract", "payment"} + + def assess(self, raw_input: str, context: dict | None = None) -> dict: + lower = raw_input.lower() + if any(term in lower for term in self.HIGH_RISK_TERMS): + return { + "risk_level": "high", + "level": "high", + "reason": "Risk detected before movement.", + "escalate": True, + "required_verification_roles": ["owner"], + } + if any(term in lower for term in self.MEDIUM_RISK_TERMS): + return { + "risk_level": "medium", + "level": "medium", + "reason": "Sensitive context detected; proceed with restraint.", + "escalate": False, + "required_verification_roles": [], + } + return { + "risk_level": "low", + "level": "low", + "reason": "No blocking risk detected.", + "escalate": False, + "required_verification_roles": [], + } diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/tests/test_decision_layer.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/tests/test_decision_layer.py new file mode 100644 index 0000000..8c239d7 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/tests/test_decision_layer.py @@ -0,0 +1,122 @@ +import os +import tempfile +import unittest + +from backend.audit_log import AuditLog +from backend.followup_engine import FollowUpEngine +from backend.memory_store import MemoryStore +from backend.playbook_interpreter import PlaybookInterpreter +from backend.reply_engine import ReplyEngine +from backend.risk_engine import RiskEngine +from backend.triage_engine import TriageEngine + + +class DecisionLayerTests(unittest.TestCase): + def test_triage_returns_exact_numeric_contract(self): + result = TriageEngine().classify( + "A client replied that the fee is too high and they need a careful response today.", + source="manual", + ) + + self.assertEqual( + set(result.keys()), + {"importance", "urgency", "required_action", "synthesis", "confidence", "playbook_id"}, + ) + self.assertEqual(result["required_action"], "reply") + self.assertEqual(result["playbook_id"], "deal-close-assistant") + self.assertIsInstance(result["importance"], int) + self.assertIsInstance(result["urgency"], int) + self.assertGreaterEqual(result["importance"], 0) + self.assertLessEqual(result["importance"], 100) + self.assertGreaterEqual(result["urgency"], 0) + self.assertLessEqual(result["urgency"], 100) + self.assertIn("synthesis", result) + + def test_reply_engine_returns_one_synthesis_and_one_action(self): + triage = { + "synthesis": "Client resistance is about price and trust.", + "required_action": "reply", + "importance": 72, + "urgency": 51, + "playbook_id": "deal-close-assistant", + } + + result = ReplyEngine().draft("Client says the fee is too high.", triage) + + self.assertEqual(set(result.keys()), {"synthesis", "recommended_action"}) + self.assertEqual(result["recommended_action"]["kind"], "reply") + self.assertEqual(set(result["recommended_action"].keys()), {"kind", "label", "payload"}) + self.assertNotIn("option", str(result).lower()) + + def test_followup_engine_creates_single_calendar_payload(self): + result = FollowUpEngine().schedule( + {"synthesis": "The decision needs a quiet check-in.", "required_action": "schedule"}, + minutes_from_now=45, + ) + + self.assertEqual(result["recommended_action"]["kind"], "schedule") + self.assertIn("next_check_timestamp", result) + self.assertIn("starts_at", result["recommended_action"]["payload"]) + self.assertEqual(result["recommended_action"]["payload"]["duration_minutes"], 15) + + def test_risk_engine_escalates_high_risk_input(self): + result = RiskEngine().assess( + "Send a threat to pressure the client into paying today.", + {"required_action": "reply"}, + ) + + self.assertEqual(result["risk_level"], "high") + self.assertEqual(result["level"], "high") + self.assertTrue(result["escalate"]) + self.assertIn("owner", result["required_verification_roles"]) + + def test_memory_store_derives_without_raw_input(self): + store = MemoryStore() + update = store.update( + user_id="owner", + raw_input="This exact private sentence must not be saved.", + decision={"recommended_action": {"kind": "reply"}}, + ) + + self.assertTrue(update["applied"]) + self.assertNotIn("This exact private sentence", str(store.export("owner"))) + + def test_audit_log_writes_signed_hash_receipt(self): + with tempfile.TemporaryDirectory() as directory: + path = os.path.join(directory, "audit.jsonl") + receipt = AuditLog(path).record( + raw_input="Private client text.", + source="manual", + action_kind="reply", + playbook_id="deal-close-assistant", + playbook_version="1.0.0", + synthesis="The client needs price reassurance without overexplaining.", + redaction_level="derived-only", + ) + + self.assertIn("id", receipt) + self.assertEqual(receipt["source"], "manual") + self.assertEqual(receipt["playbook_id"], "deal-close-assistant") + self.assertEqual(receipt["playbook_version"], "1.0.0") + self.assertTrue(receipt["signatures"]) + with open(path, "r", encoding="utf-8") as handle: + content = handle.read() + self.assertIn("input_hash", content) + self.assertIn("signatures", content) + self.assertNotIn("Private client text.", content) + + def test_playbook_interpreter_loads_seed_playbooks(self): + interpreter = PlaybookInterpreter() + result = interpreter.select("The investor asks for the term sheet before Friday.") + + self.assertGreaterEqual(len(interpreter.playbooks), 12) + self.assertEqual( + set(result.keys()), + {"playbook_id", "title", "recommended_action", "version"}, + ) + self.assertEqual(result["playbook_id"], "investor-ir-triage") + self.assertIn(result["recommended_action"], {"reply", "schedule", "resolve", "escalate"}) + + +if __name__ == "__main__": + unittest.main() diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/triage_engine.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/triage_engine.py new file mode 100644 index 0000000..11deae6 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/backend/triage_engine.py @@ -0,0 +1,129 @@ +"""Purpose: deterministic Intake Layer triage for MindReply. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +from dataclasses import dataclass + + +ACTIONS = {"reply", "schedule", "resolve", "escalate"} + + +@dataclass(frozen=True) +class TriageResult: + importance: int + urgency: int + required_action: str + synthesis: str + confidence: float + playbook_id: str + + def to_dict(self) -> dict: + return { + "importance": self.importance, + "urgency": self.urgency, + "required_action": self.required_action, + "synthesis": self.synthesis, + "confidence": self.confidence, + "playbook_id": self.playbook_id, + } + + +class TriageEngine: + HIGH_RISK_TERMS = {"threat", "force", "blackmail", "harass", "illegal", "lawsuit", "regulator", "breach"} + REPLY_TERMS = {"reply", "client", "customer", "email", "message", "fee", "price", "response", "proposal"} + SCHEDULE_TERMS = {"follow up", "check in", "tomorrow", "next week", "calendar", "meeting", "later"} + + def classify( + self, + raw_input: str, + source: str = "manual", + user_context: dict | None = None, + device_privacy_flag: bool = False, + playbook_id: str | None = None, + ) -> dict: + text = " ".join(raw_input.split()).strip() + lower = text.lower() + matched_playbook = playbook_id or self._match_playbook(lower) + action = self._required_action(lower, matched_playbook) + urgency = self._urgency(lower, action) + importance = self._importance(lower, source, matched_playbook, action) + + return TriageResult( + importance=importance, + urgency=urgency, + required_action=action, + synthesis=self._synthesis(text, action), + confidence=0.86 if text else 0.0, + playbook_id=matched_playbook, + ).to_dict() + + def _required_action(self, lower: str, playbook_id: str) -> str: + if any(term in lower for term in self.HIGH_RISK_TERMS) or "risk" in playbook_id: + return "escalate" + if any(term in lower for term in self.SCHEDULE_TERMS): + return "schedule" + if any(term in lower for term in self.REPLY_TERMS): + return "reply" + if playbook_id in {"finance-approval-flow", "compliance-audit-trail", "legal-risk-flag"}: + return "escalate" + return "resolve" + + def _urgency(self, lower: str, action: str) -> int: + score = 35 + if any(term in lower for term in ["today", "urgent", "immediately", "deadline", "now"]): + score += 42 + if action == "escalate": + score += 18 + return min(score, 100) + + def _importance(self, lower: str, source: str, playbook_id: str, action: str) -> int: + score = 48 + if source in {"gmail", "calendar", "extension"}: + score += 6 + if playbook_id != "clear-next-move": + score += 18 + if action == "escalate": + score += 22 + if any(term in lower for term in ["board", "investor", "legal", "wire", "launch"]): + score += 12 + return min(score, 100) + + def _match_playbook(self, lower: str) -> str: + if any(term in lower for term in ["investor", "shareholder", "funding", "term sheet"]): + return "investor-ir-triage" + if any(term in lower for term in ["legal", "lawsuit", "regulator", "contract"]): + return "legal-risk-flag" + if any(term in lower for term in ["press", "journalist", "statement", "crisis"]): + return "pr-crisis-triage" + if any(term in lower for term in ["fee", "price", "proposal", "deal"]): + return "deal-close-assistant" + if any(term in lower for term in ["meeting", "follow up", "next step"]): + return "meeting-outcome-actioner" + if any(term in lower for term in ["audit", "policy", "breach"]): + return "compliance-audit-trail" + if any(term in lower for term in ["client", "customer", "complaint"]): + return "customer-escalation" + if any(term in lower for term in ["candidate", "interview", "hire"]): + return "hiring-decision-helper" + if any(term in lower for term in ["invoice", "payment", "wire", "approval"]): + return "finance-approval-flow" + if any(term in lower for term in ["launch", "release", "ship"]): + return "product-launch-gatekeeper" + if any(term in lower for term in ["family", "personal", "appointment"]): + return "personal-life-triage" + if any(term in lower for term in ["inbox", "newsletter", "fyi"]): + return "exec-inbox-zero" + return "clear-next-move" + + def _synthesis(self, text: str, action: str) -> str: + if not text: + return "No usable input was provided." + if action == "escalate": + return "This carries risk and needs review before movement." + if action == "reply": + return "This needs a calm response that reduces pressure and preserves the relationship." + if action == "schedule": + return "This needs a quiet follow-up moment rather than more wording now." + return "This can be closed with a clear record and no further movement." diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/README.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/README.md new file mode 100644 index 0000000..b86407e --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/README.md @@ -0,0 +1,54 @@ +# MRagent ChatGPT App Scaffold + +This folder defines the developer contract for exposing MRagent as a ChatGPT App or MCP-backed tool surface. + +The public site remains minimal. This scaffold does not include credentials, private strategy, provider keys, or founder-only operating notes. + +## Tool Contract + +`mragent.decision` accepts one text intake and returns the same shape as `POST /api/intake`: + +```json +{ + "synthesis": "string", + "recommendedAction": { + "kind": "reply | schedule | resolve | escalate", + "label": "string", + "payload": {} + }, + "risk": { + "level": "low | medium | high", + "reason": "string", + "escalate": false + }, + "memoryUpdate": { + "applied": true, + "summary": "string" + }, + "receipt": { + "id": "string", + "timestamp": "ISO string", + "source": "manual | gmail | calendar | extension", + "playbookId": "string", + "playbookVersion": "string", + "redactionLevel": "derived-only", + "signature": "string" + } +} +``` + +`mragent.chat` accepts a friendly user message and returns the `/api/agent` response with an addressable receipt. Raw input is not stored by default. + +## Build Notes + +- Keep generated text private to the user session unless explicit consent is captured. +- Never expose provider names, hidden prompts, staffing claims, tokens, or internal strategy. +- Every response must contain one synthesis and one recommended action. +- High-risk text must escalate instead of drafting a response. + +## Deployment + +The live Next.js app already exposes the required endpoints. A future MCP server can import these contracts and proxy to: + +- `POST https://www.mind-reply.com/api/intake` +- `POST https://www.mind-reply.com/api/agent` diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/mragent-tools.json b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/mragent-tools.json new file mode 100644 index 0000000..4332fab --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/chatgpt-app/mragent-tools.json @@ -0,0 +1,58 @@ +{ + "name": "mindreply-mragent", + "version": "0.1.0", + "tools": [ + { + "name": "mragent.decision", + "description": "Return one synthesis, one recommended action, risk, memory update, and receipt for a supplied intake.", + "input_schema": { + "type": "object", + "additionalProperties": false, + "required": ["input"], + "properties": { + "input": { "type": "string", "minLength": 1 }, + "source": { + "type": "string", + "enum": ["manual", "gmail", "calendar", "extension"], + "default": "manual" + }, + "userId": { "type": "string" }, + "consentFullContent": { "type": "boolean", "default": false }, + "devicePrivacyFlag": { "type": "boolean", "default": false } + } + }, + "output_schema": { + "type": "object", + "required": ["synthesis", "recommendedAction", "risk", "memoryUpdate", "receipt"], + "properties": { + "synthesis": { "type": "string" }, + "recommendedAction": { + "type": "object", + "required": ["kind", "label", "payload"], + "properties": { + "kind": { "type": "string", "enum": ["reply", "schedule", "resolve", "escalate"] }, + "label": { "type": "string" }, + "payload": { "type": "object" } + } + }, + "risk": { "type": "object" }, + "memoryUpdate": { "type": "object" }, + "receipt": { "type": "object" } + } + } + }, + { + "name": "mragent.chat", + "description": "Return a friendly MRagent response that still maps to the decision receipt contract.", + "input_schema": { + "type": "object", + "additionalProperties": false, + "required": ["input"], + "properties": { + "input": { "type": "string", "minLength": 1 }, + "userId": { "type": "string" } + } + } + } + ] +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/background.js b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/background.js new file mode 100644 index 0000000..af43a82 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/background.js @@ -0,0 +1,42 @@ +const DEFAULT_ENDPOINT = "https://www.mind-reply.com/api/intake"; + +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.create({ + id: "mindreply-intake", + title: "MindReply: clarify next move", + contexts: ["selection"] + }); +}); + +chrome.contextMenus.onClicked.addListener(async (info, tab) => { + if (!tab?.id || !info.selectionText) return; + const decision = await requestDecision(info.selectionText); + await chrome.tabs.sendMessage(tab.id, { type: "MINDREPLY_DECISION", decision }); +}); + +chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => { + if (message?.type !== "MINDREPLY_DECIDE" || !message.text) return false; + requestDecision(message.text) + .then((decision) => sendResponse({ ok: true, decision })) + .catch((error) => sendResponse({ ok: false, error: error.message })); + return true; +}); + +async function requestDecision(text) { + const stored = await chrome.storage.sync.get("endpoint"); + const endpoint = stored.endpoint || DEFAULT_ENDPOINT; + const response = await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + input: text, + source: "extension", + consentFullContent: false, + devicePrivacyFlag: false + }) + }); + if (!response.ok) { + throw new Error(`MindReply request failed with ${response.status}`); + } + return response.json(); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/content.js b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/content.js new file mode 100644 index 0000000..a435c95 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/content.js @@ -0,0 +1,78 @@ +chrome.runtime.onMessage.addListener((message) => { + if (message.type !== "MINDREPLY_DECISION") return; + renderDecision(message.decision); +}); + +document.addEventListener("mouseup", () => { + const selection = window.getSelection()?.toString().trim(); + if (!selection || selection.length < 12) return; + showInlineTrigger(selection); +}); + +function showInlineTrigger(text) { + removeElement("mindreply-inline-trigger"); + const trigger = document.createElement("button"); + trigger.id = "mindreply-inline-trigger"; + trigger.type = "button"; + trigger.textContent = "MindReply"; + trigger.addEventListener("click", () => { + trigger.disabled = true; + chrome.runtime.sendMessage({ type: "MINDREPLY_DECIDE", text }, (response) => { + trigger.remove(); + if (!response?.ok) { + renderError(response?.error || "MindReply could not read that selection."); + return; + } + renderDecision(response.decision); + }); + }); + document.body.appendChild(trigger); +} + +function renderDecision(decision) { + removeElement("mindreply-inline-decision"); + const panel = document.createElement("aside"); + panel.id = "mindreply-inline-decision"; + + const title = document.createElement("strong"); + title.textContent = "MindReply"; + panel.appendChild(title); + + const synthesis = document.createElement("p"); + synthesis.textContent = decision?.synthesis || "One synthesis is ready."; + panel.appendChild(synthesis); + + const action = document.createElement("button"); + action.type = "button"; + action.textContent = decision?.recommendedAction?.label || "Proceed when ready"; + panel.appendChild(action); + + const details = document.createElement("small"); + const risk = decision?.risk?.level || "low"; + const receipt = decision?.receipt?.id || "pending"; + details.textContent = `Risk: ${risk} · Receipt: ${receipt}`; + panel.appendChild(details); + + const close = document.createElement("button"); + close.type = "button"; + close.textContent = "Close"; + close.setAttribute("aria-label", "Close MindReply panel"); + close.addEventListener("click", () => panel.remove()); + panel.appendChild(close); + + document.body.appendChild(panel); +} + +function renderError(message) { + renderDecision({ + synthesis: message, + recommendedAction: { label: "Try again" }, + risk: { level: "low" }, + receipt: { id: "none" } + }); +} + +function removeElement(id) { + const existing = document.getElementById(id); + if (existing) existing.remove(); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/styles.css b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/styles.css new file mode 100644 index 0000000..a9dd711 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/edge/extension/styles.css @@ -0,0 +1,56 @@ +#mindreply-inline-trigger { + position: fixed; + right: 20px; + bottom: 92px; + z-index: 2147483647; + border: 1px solid rgba(201, 169, 97, 0.55); + border-radius: 999px; + padding: 9px 14px; + background: #081121; + color: #f8f5f0; + box-shadow: 0 12px 34px rgba(0, 0, 0, 0.26); + font: 700 13px Inter, system-ui, sans-serif; +} + +#mindreply-inline-decision { + position: fixed; + right: 20px; + bottom: 20px; + z-index: 2147483647; + max-width: min(360px, calc(100vw - 40px)); + padding: 18px; + border: 1px solid rgba(201, 169, 97, 0.45); + border-radius: 14px; + background: #081121; + color: #f8f5f0; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.35); + font-family: Inter, system-ui, sans-serif; +} + +#mindreply-inline-decision p { + margin: 10px 0; + line-height: 1.5; +} + +#mindreply-inline-decision button { + width: 100%; + border: 0; + border-radius: 999px; + padding: 10px 14px; + background: #c9a961; + color: #081121; + font-weight: 700; +} + +#mindreply-inline-decision button + button { + margin-top: 8px; + background: transparent; + color: #cdd6e4; + border: 1px solid rgba(205, 214, 228, 0.26); +} + +#mindreply-inline-decision small { + display: block; + margin: 10px 0; + color: #cdd6e4; +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/calendar_connector.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/calendar_connector.py new file mode 100644 index 0000000..4d46f8d --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/calendar_connector.py @@ -0,0 +1,60 @@ +"""Purpose: calendar adapter for one follow-up or escalation event. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +import json +import os +from datetime import datetime, timedelta, timezone +from pathlib import Path + + +class CalendarConnector: + """Writes a single event payload to an outbox or returns it to a host adapter.""" + + def __init__(self, path: str | None = None, env: dict | None = None) -> None: + self.env = env or os.environ + self.path = Path(path or self.env.get("MINDREPLY_CALENDAR_OUTBOX", "calendar-outbox.jsonl")) + + def ready(self) -> bool: + return bool(self.env.get("MINDREPLY_CALENDAR_PROVIDER") or self.env.get("MINDREPLY_CALENDAR_OUTBOX")) + + def event_from_decision(self, decision: dict, delay_minutes: int = 60, persist: bool = False) -> dict: + action = decision.get("recommendedAction") or decision.get("recommended_action") or {} + kind = action.get("kind", "schedule") + payload = action.get("payload", {}) if isinstance(action.get("payload", {}), dict) else {} + starts_at = payload.get("starts_at") or payload.get("startsAt") or self._timestamp(delay_minutes) + title = payload.get("title") or ("MindReply review" if kind == "escalate" else "MindReply follow-up") + event = { + "title": title, + "starts_at": starts_at, + "duration_minutes": int(payload.get("duration_minutes", 15)), + "kind": "escalation" if kind == "escalate" else "follow_up", + "synthesis": decision.get("synthesis", "Decision receipt available."), + "receipt_id": (decision.get("receipt") or {}).get("id"), + } + if persist: + self._write(event) + return event + + def create_followup(self, title: str, delay_minutes: int = 60, note: str = "", persist: bool = True) -> dict: + event = { + "title": title, + "starts_at": self._timestamp(delay_minutes), + "duration_minutes": 15, + "kind": "follow_up", + "synthesis": note, + "receipt_id": None, + } + if persist: + self._write(event) + return event + + def _timestamp(self, delay_minutes: int) -> str: + return (datetime.now(timezone.utc) + timedelta(minutes=delay_minutes)).isoformat() + + def _write(self, event: dict) -> None: + self.path.parent.mkdir(parents=True, exist_ok=True) + with self.path.open("a", encoding="utf-8") as handle: + handle.write(json.dumps(event, sort_keys=True) + "\n") diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/gmail_connector.py b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/gmail_connector.py new file mode 100644 index 0000000..8477e4a --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-build/src/integrations/gmail_connector.py @@ -0,0 +1,102 @@ +"""Purpose: Gmail/IMAP intake adapter that sends only safe candidates into MindReply. +Local test: python -m unittest discover src +""" + +from __future__ import annotations + +import os +from dataclasses import dataclass +from typing import Iterable + + +@dataclass(frozen=True) +class MailCandidate: + source: str + input: str + consent_full_content: bool + device_privacy_flag: bool + metadata: dict + + def to_intake(self) -> dict: + return { + "source": self.source, + "input": self.input, + "consent_full_content": self.consent_full_content, + "device_privacy_flag": self.device_privacy_flag, + "metadata": self.metadata, + } + + +class GmailConnector: + """Header/snippet-first connector for Gmail or standard IMAP. + + The connector avoids network reads unless the host process has configured + credentials. Raw body text is included only when explicit consent is supplied + for that request. + """ + + REQUIRED_ENV = ("MINDREPLY_IMAP_HOST", "MINDREPLY_IMAP_USER", "MINDREPLY_IMAP_PASSWORD") + + def __init__(self, env: dict | None = None) -> None: + self.env = env or os.environ + + def ready(self) -> bool: + return all(self.env.get(name) for name in self.REQUIRED_ENV) + + def candidate_from_message( + self, + subject: str, + sender: str, + snippet: str, + message_id: str | None = None, + consent_full_content: bool = False, + body: str | None = None, + device_privacy_flag: bool = False, + ) -> dict: + safe_subject = self._compact(subject) + safe_sender = self._compact(sender) + safe_snippet = self._compact(snippet, limit=280) + content = body if consent_full_content and body else f"From: {safe_sender}\nSubject: {safe_subject}\nSnippet: {safe_snippet}" + candidate = MailCandidate( + source="gmail", + input=content, + consent_full_content=bool(consent_full_content and body), + device_privacy_flag=device_privacy_flag, + metadata={ + "message_id": message_id, + "subject_present": bool(safe_subject), + "sender_present": bool(safe_sender), + "body_included": bool(consent_full_content and body), + }, + ) + return candidate.to_intake() + + def fetch_candidates(self, limit: int = 10) -> list[dict]: + """Return candidate payloads when a host process wires IMAP access. + + This repository keeps the connector deterministic and dependency-light. + Production workers can call `candidate_from_message` after reading mail + through their approved Gmail or IMAP client. + """ + + if not self.ready(): + return [] + return [] + + def batch_from_headers(self, messages: Iterable[dict]) -> list[dict]: + return [ + self.candidate_from_message( + subject=str(message.get("subject", "")), + sender=str(message.get("sender", "")), + snippet=str(message.get("snippet", "")), + message_id=message.get("message_id"), + consent_full_content=bool(message.get("consent_full_content", False)), + body=message.get("body"), + device_privacy_flag=bool(message.get("device_privacy_flag", False)), + ) + for message in messages + ] + + def _compact(self, value: str, limit: int = 160) -> str: + compact = " ".join(value.split()) + return compact[:limit] diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/decision/route.ts b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/decision/route.ts new file mode 100644 index 0000000..275abf0 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/decision/route.ts @@ -0,0 +1,39 @@ +import { NextResponse } from "next/server"; + +type ActionKind = "reply" | "schedule" | "resolve" | "escalate"; +type Role = "owner" | "reviewer" | "operator"; +type Redaction = "metadata" | "partial" | "full"; + +const actions = new Set(["reply", "schedule", "resolve", "escalate"]); + +export async function POST(request: Request) { + const body = (await request.json().catch(() => ({}))) as { + owner_email?: string; + role?: Role; + synthesis?: string; + recommended_action?: ActionKind; + consent_granted?: boolean; + redaction_level?: Redaction; + }; + + const ownerEmail = String(body.owner_email ?? "").trim().toLowerCase(); + const role = body.role ?? "owner"; + const action = body.recommended_action && actions.has(body.recommended_action) ? body.recommended_action : "reply"; + const consentGranted = body.consent_granted === true; + const exportAllowed = Boolean(ownerEmail && role === "owner" && consentGranted); + + return NextResponse.json({ + decision_id: crypto.randomUUID(), + timestamp: new Date().toISOString(), + owner_email: ownerEmail, + role, + synthesis: String(body.synthesis ?? "No synthesis provided.").replace(/\s+/g, " ").trim().slice(0, 320), + recommended_action: action, + consent_granted: consentGranted, + redaction_level: body.redaction_level ?? "metadata", + export_allowed: exportAllowed, + reason: exportAllowed + ? "Owner consent verified; export can be prepared." + : "Owner export is held until owner identity and consent are verified.", + }); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/export/route.ts b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/export/route.ts new file mode 100644 index 0000000..505b47b --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/app/api/owner/export/route.ts @@ -0,0 +1,46 @@ +import { NextResponse } from "next/server"; + +type ActionKind = "reply" | "schedule" | "resolve" | "escalate"; + +export async function POST(request: Request) { + const body = (await request.json().catch(() => ({}))) as { + owner_email?: string; + synthesis?: string; + recommended_action?: ActionKind; + redaction_level?: string; + receipt_id?: string; + export_allowed?: boolean; + }; + + if (body.export_allowed !== true) { + return NextResponse.json( + { + prepared: false, + reason: "Owner export is held until owner identity and consent are verified.", + }, + { status: 403 }, + ); + } + + const action = body.recommended_action ?? "reply"; + const bodyText = [ + "MindReply owner decision package", + `Synthesis: ${String(body.synthesis ?? "No synthesis provided.").replace(/\s+/g, " ").trim().slice(0, 320)}`, + `Recommended action: ${action}`, + `Redaction level: ${body.redaction_level ?? "metadata"}`, + `Receipt: ${body.receipt_id ?? crypto.randomUUID()}`, + "Raw content is excluded unless the owner explicitly exports it.", + ].join("\n"); + + return NextResponse.json({ + prepared: true, + export_id: crypto.randomUUID(), + timestamp: new Date().toISOString(), + to: String(body.owner_email ?? "").trim().toLowerCase(), + subject: "MindReply owner decision package", + body: bodyText, + recommended_action: action, + redaction_level: body.redaction_level ?? "metadata", + delivery_status: "prepared", + }); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/advanced_team_blueprint.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/advanced_team_blueprint.md new file mode 100644 index 0000000..009bd47 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/advanced_team_blueprint.md @@ -0,0 +1,65 @@ +# Advanced Team Blueprint + +MindReply keeps one Knowledge Lead and replaces broad agent sprawl with an advanced execution team. + +## Team Shape + +### Knowledge Lead + +Owns category language, playbooks, privacy rules, owner rules, and product memory. + +Friction removed: the owner no longer has to re-explain the category each time. + +Decision simplified: every team lane receives one trusted source of truth. + +Trust built: language, privacy, and owner rules stay consistent. + +Copy resistance: a cheaper copy can imitate screens, but not accumulated category memory and signed operating rules. + +Category fit: this is the nervous system memory layer. + +### Social Signal Lead + +Turns founder updates, proof points, and public pressure into one discreet growth move. + +Friction removed: social presence becomes a single prepared action, not a noisy content calendar. + +Decision simplified: the owner sees one message or one public move. + +Trust built: public language stays calm, precise, and category-safe. + +Copy resistance: the voice compounds through repeated, disciplined proof. + +Category fit: this extends Decision Infrastructure into daily visibility. + +### Expert Trust Lead + +Turns proof, founder notes, privacy claims, and use cases into authority assets. + +Friction removed: sales and trust material no longer starts from a blank page. + +Decision simplified: the team gets one proof-led asset to publish, send, or hold. + +Trust built: every claim is tied to proof, receipt logic, or owner-approved language. + +Copy resistance: trust comes from evidence and restraint, not louder claims. + +Category fit: this makes the Executive Nervous System understandable to serious buyers. + +### Azure Security Lead + +Protects owner access, export consent, receipts, runtime health, and observability. + +Friction removed: security work becomes one control action at a time. + +Decision simplified: high-risk items escalate; healthy items remain quiet. + +Trust built: the owner path, export path, and runtime path are visible and reviewable. + +Copy resistance: infrastructure discipline is harder to copy than a homepage. + +Category fit: this is the protective reflex of the nervous system. + +## Operating Rule + +Each lane returns one synthesis and one recommended action. If the lane cannot reduce the work, it escalates. diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/hourly_owner_goal_prompt.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/hourly_owner_goal_prompt.md new file mode 100644 index 0000000..a467a45 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/hourly_owner_goal_prompt.md @@ -0,0 +1,37 @@ +# Hourly Owner Goal Prompt + +This is private operating context for internal lanes only. + +## Prime Directive + +Revenue-first. Stop builder thinking. Fix only buying friction. Sell Website Completion Package first. + +## Active Lanes + +- Revenue Lead: package, assisted close, pricing copy. +- Frontend Lead: homepage clarity, trust proof, authority layer. +- Deploy Lead: Vercel blocker, build status, domain state. +- Security Lead: secrets, auth and payment safety, redacted owner evidence. +- Growth Lead: LinkedIn and email lines, search intent, ad proof. +- Product Lead: first three-minute value, upgrade trigger. +- Reporting Lead: email and Slack delivery receipt. + +## Hourly Rule + +Each hour returns one synthesis and one recommended action for the owner. + +## Selling Rule + +Lead with Website Completion Package. Every change must reduce buying friction, strengthen the premium authority layer, add trust proof, or make assisted close easier. + +## Priority Rule + +Deprioritize Slack and MCP work unless it closes revenue this hour. + +## Security Boundary + +Reports are private, owner-only, and redacted. No raw client material leaves the workspace. Missing provider secrets create a blocked receipt. + +## Public Boundary + +No public page may claim active internal staff count or reveal internal prompts. diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/observability_contract.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/observability_contract.md new file mode 100644 index 0000000..84bcc72 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/observability_contract.md @@ -0,0 +1,43 @@ +# Observability Contract + +MindReply observability stays quiet. It records system health and owner-critical events without demanding attention. + +## Events + +- intake.received +- action.prepared +- risk.blocked +- memory.updated +- owner.decision.prepared +- owner.export.held +- owner.export.prepared + +## Required Fields + +- event_name +- timestamp +- decision_id +- recommended_action +- redaction_level +- actor_role +- receipt_id + +## Alert Conditions + +- High-risk action without owner review. +- Export request without consent. +- Missing receipt. +- Mail delivery provider unavailable. +- Repeated memory write failure. + +## Friction Removed + +The owner does not need to inspect logs unless a trust boundary is crossed. + +## Decision Simplified + +Every alert maps to one owner action: review, hold, retry, or close. + +## Trust Built + +The system proves when it acted, when it paused, and why. diff --git a/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/owner_security_blueprint.md b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/owner_security_blueprint.md new file mode 100644 index 0000000..79bc440 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_executive-nervous-system-rebrand/docs/owner_security_blueprint.md @@ -0,0 +1,48 @@ +# Owner Security Blueprint + +MindReply can work directly with the owner only through a verified decision path. + +## Owner Decision Rule + +Owner-level execution requires: + +- Verified owner email. +- Explicit consent. +- One synthesis. +- One recommended action. +- Redaction level. +- Signed receipt. + +If any item is missing, export is held. + +## Owner Mail Package + +The owner mail package contains: + +- Source label. +- Synthesis. +- Recommended action. +- Redaction level. +- Receipt identifier. + +Raw content stays excluded unless the owner explicitly exports it. + +## Friction Removed + +The owner no longer needs to inspect every source thread before seeing the decision shape. + +## Decision Simplified + +The owner sees one recommended action and one reason to act, pause, or review. + +## Trust Built + +Every owner package is consented, redacted, and tied to a receipt. + +## Copy Resistance + +A cheaper copy can imitate the screen. It cannot easily reproduce consent history, role gates, redacted receipts, and repeated private behavior. + +## Category Fit + +This is Decision Infrastructure: the layer between input and action, with the owner kept in control. diff --git a/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/front_end_operating_pack.md b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/front_end_operating_pack.md new file mode 100644 index 0000000..bb302ee --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/front_end_operating_pack.md @@ -0,0 +1,106 @@ +# MRagent Front End Operating Pack + +This pack explains how the current MindReply front end works and what each surface is responsible for. + +## Surfaces + +- `/`: the public front door. It now presents immediate operational relief, premium communication intelligence, the paid ladder, trust proof, promotion readiness, and owner-visible observability. +- `/agent`: the live MRagent session. It accepts charged text, explains the slow reply, and returns one synthesis, one recommended move, risk state, memory summary, and a quiet receipt. +- `/pack`: the private completion surface. It shows delivery readiness, revenue truth, promotion queue state, reporting lanes, and confirmed destination status. +- `/privacy`: the restraint page. It explains why raw pressure is not kept as a default record. +- `/mcp`: the ChatGPT App connection surface for Developer Mode. + +## How The Interaction Works + +1. A user places the charged message or hesitation into MRagent. +2. The browser calls `/api/agent` with the message and source. +3. If the model/provider path is unavailable, the app falls back to `/api/intake`. +4. The decision layer returns a structured result: synthesis, mind read, recommended action, risk, memory summary, and receipt. +5. The UI renders the answer through the AI Elements message component, then displays the structured receipt below it. +6. Persistence remains privacy-safe: no raw input is stored by default, and Blob storage is private when configured. + +## Full Front End Map + +The front end is intentionally not a marketing-only page. It is the usable product surface: + +- Hero: `Reclaim 2+ hours daily within 24 hours` plus the first-output promise. +- Authority layer: executive communication intelligence, professional lexicons, tone, structure, empathy, persuasion, and compliance-aware refinement. +- Paid path: free first output, credits/package, Growth, and Pro. +- Trust proof: stored vs not stored, memory consent, sensitive-work review, request safety, and owner logs. +- Promotion readiness: MRadvertisingTeam, assisted close, and revenue-readiness language without fake posting claims. +- Completion pack: delivery status, real revenue counters, report lanes, and blockers. + +## 40-Minute Reporting + +The Codex app heartbeat `mindreply-40-minute-revenue-and-security-updates` runs every 40 minutes and produces a 7-section owner-facing report. + +The report sections are: + +1. Status +2. Changed +3. Security / Owner Decisions +4. Revenue Move +5. Website / Frontend +6. Blocked +7. Next Safe Action + +Delivery is intentionally gated. The report writes to the thread first, then attempts Outlook delivery to `ANGELLLKR@GMAIL.COM`. Slack or other platform posting requires a connected destination and explicit approval in the active thread. + +## Revenue-First Rule + +This week, the priority is not infrastructure breadth. The priority is buying friction: + +- make Website Completion Package the first paid offer; +- keep the first free output visible; +- add trust proof before asking for sensitive input; +- make credits, package, Growth, and Pro visible; +- use assisted close through email, booking, and direct follow-up; +- pause Slack/MCP polish unless it directly closes revenue. + +## Automation Guardrails + +The safe automation posture is: + +- prepare campaign material, do not publish externally without connected accounts and explicit approval; +- report blockers directly instead of pretending an integration works; +- avoid stealth posting, guaranteed reach, or guaranteed revenue language; +- keep deployment work on guarded branches until production approval is explicit; +- keep transaction and revenue counters tied to real sources. + +## Motion Direction + +A Remotion spot should show MindReply without loud product theatrics: + +- Scene 1: a charged message sits on a quiet field. +- Scene 2: the words dim while pressure, protection, risk, and next move become visible. +- Scene 3: one composed reply appears beside a narrow receipt. +- Scene 4: the Completion Pack receives the status without exposing the original text. + +Motion should be restrained: slow fades, precise reveals, no fake revenue, no exaggerated claims. + +## Promotion Positioning + +Promotion should not promise hidden posting, guaranteed revenue, or platform-wide distribution without connected accounts. The safe posture is: + +- publish only where accounts and permissions are real; +- report what was prepared, sent, or blocked; +- keep the language private, elegant, and specific; +- never invent transactions, audience response, or revenue. + +Use this line as the campaign spine: + +> Reclaim the hour. Rescue the message. Keep the receipt narrow. + +## Figma State + +- Existing preview: https://www.figma.com/design/QLximv9mLCIwQB2GPgBgeG +- New direction file: https://www.figma.com/design/PuRHREBbTixXGxPsBEI1yz +- FigJam operating map: https://www.figma.com/board/G0lSiegpqHSoQDpmgoYKDL + +The connected Figma account currently reports a Starter plan with a View seat. That blocks creating new editable files, placing new frames, FigJam edits, and Code Connect mappings from this session. Once an edit-capable seat is available, the next Figma step is to place the editable frames and map them to `app/page.tsx`, `components/MRAgentChat.tsx`, and `app/pack/page.tsx`. + +## Vercel And Observability + +`vercel.json` disables deployments for every branch except `main`, which prevents duplicate preview deployment spam while frontend work is in progress. Production deployment still requires an explicit merge to `main` and a quota-safe release window. + +Vercel Speed Insights is mounted in `app/layout.tsx`. This branch adds structured owner logs for `/api/agent`, `/api/intake`, and `/mcp`: request source, risk state, fallback/persistence status, batch size, rejection reason, and no raw user text. diff --git a/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/security_owner_decision_report.md b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/security_owner_decision_report.md new file mode 100644 index 0000000..2d7dc33 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/docs/security_owner_decision_report.md @@ -0,0 +1,40 @@ +# MindReply Security Owner Decision Report + +Date: 2026-06-09 +Branch: `codex/full-frontend-platform-pack` + +## Status + +A formal Codex Security Deep Security Scan was attempted, but the six delegated discovery worker handles disappeared before a complete round could be collected. Partial artifacts were preserved under the local scan directory, but the round did not meet the Deep Security completion rules, so it must not be represented as a completed deep scan. + +Local artifact root: + +`C:\Users\angel\AppData\Local\Temp\codex-security-scans\MindReply\github-codex-full-frontend-platform-pack_20260609T030000Z` + +## Fixed In This Branch + +- Added `lib/request-safety.ts` with request body limits, text input limits, owner-trusted user id handling, and structured owner logs. +- Updated `/api/agent` to reject missing, invalid, oversized, or too-long input and log safe request status without raw user text. +- Updated `/api/intake` to reject unsafe input and stop trusting client-provided `userId` unless `MINDREPLY_OWNER_API_TOKEN` authorizes the request. +- Updated `/mcp` to reject oversized JSON bodies, limit JSON-RPC batch size, and enforce the same text-input cap before MRagent tool calls. +- Updated the homepage to include the commercial trust layer: what is stored, what is not stored, memory consent, sensitive-work review, and owner-visible telemetry. + +## Owner Decisions Needed + +1. Auth mode for production user accounts: choose the identity provider before storing user-owned memory or payment-linked activity. +2. Owner API token policy: set `MINDREPLY_OWNER_API_TOKEN` only for trusted owner/admin API usage; do not expose it to the browser. +3. Memory consent: decide the exact UX copy and storage rule before enabling persistent long-term memory. +4. Sensitive-work review: decide which legal, finance, executive, or client-facing cases must be held for human review before send-ready output is treated as final. +5. Deep scan rerun: rerun Codex Security only when delegated workers can complete a full six-worker round, or use the ordinary repository security scan workflow as the fallback. + +## Current Expansion Rule + +MindReply can expand through lanes, not uncontrolled automation: + +- Security lane: owner decisions, request safety, privacy proof, scan reports. +- Revenue lane: Website Completion Package, assisted close, pricing ladder, outbound copy. +- Frontend lane: first free output, premium authority layer, trust proof, paid path. +- Integration lane: email, Slack, ChatGPT App, and future auth only when credentials and approval are explicit. +- Observability lane: structured logs and safe status reports without raw sensitive content. + +No public posting, production deployment, credential changes, spending, or destructive operations should occur without explicit owner approval in the active thread. diff --git a/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/lib/request-safety.ts b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/lib/request-safety.ts new file mode 100644 index 0000000..e4f6f1a --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_full-frontend-platform-pack/lib/request-safety.ts @@ -0,0 +1,64 @@ +export const MAX_TEXT_INPUT_CHARS = 8000; +export const MAX_JSON_BODY_BYTES = 32768; +export const MAX_MCP_BATCH_ITEMS = 8; + +export class SafeRequestError extends Error { + status: number; + code: string; + + constructor(status: number, code: string, message: string) { + super(message); + this.name = "SafeRequestError"; + this.status = status; + this.code = code; + } +} + +export async function readJsonRequest(request: Request, maxBytes = MAX_JSON_BODY_BYTES): Promise { + const text = await request.text().catch(() => ""); + if (!text.trim()) throw new SafeRequestError(400, "empty_body", "Request body is required."); + + const bodyBytes = new TextEncoder().encode(text).length; + if (bodyBytes > maxBytes) { + throw new SafeRequestError(413, "body_too_large", `Request body must be ${maxBytes} bytes or smaller.`); + } + + try { + return JSON.parse(text) as unknown; + } catch { + throw new SafeRequestError(400, "invalid_json", "Request body must be valid JSON."); + } +} + +export function assertTextInputWithinLimit(input: string, maxChars = MAX_TEXT_INPUT_CHARS) { + if (input.length > maxChars) { + throw new SafeRequestError(413, "input_too_large", `Input must be ${maxChars} characters or fewer.`); + } +} + +export function trustedClientUserId(request: Request, body: unknown) { + const ownerToken = process.env.MINDREPLY_OWNER_API_TOKEN; + const authorization = request.headers.get("authorization") || ""; + const bearer = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : ""; + + if (!ownerToken || bearer !== ownerToken) return undefined; + + const record = body && typeof body === "object" ? (body as Record) : {}; + const userId = typeof record.userId === "string" ? record.userId.trim() : ""; + return userId ? userId.slice(0, 128) : undefined; +} + +export function ownerSecurityLog(event: string, details: Record) { + const safeDetails = Object.fromEntries( + Object.entries(details).filter(([, value]) => value !== undefined), + ); + + console.info( + JSON.stringify({ + service: "mindreply", + event, + timestamp: new Date().toISOString(), + ...safeDetails, + }), + ); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/agents/page.tsx b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/agents/page.tsx new file mode 100644 index 0000000..9ef1727 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/agents/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function AgentsRedirectPage() { + redirect("/agent"); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agent/orchestrate/route.ts b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agent/orchestrate/route.ts new file mode 100644 index 0000000..39fb3b6 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agent/orchestrate/route.ts @@ -0,0 +1,15 @@ +import { NextRequest, NextResponse } from "next/server"; +import { orchestrateGoal } from "@/lib/moa"; + +export async function POST(req: NextRequest) { + try { + const { goal } = await req.json(); + if (!goal || !String(goal).trim()) { + return NextResponse.json({ error: "goal is required" }, { status: 400 }); + } + + return NextResponse.json(orchestrateGoal({ goal: String(goal) })); + } catch { + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agents/route.ts b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agents/route.ts new file mode 100644 index 0000000..d5ac01c --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/app/api/agents/route.ts @@ -0,0 +1,11 @@ +import { NextResponse } from "next/server"; +import { getAgentBlueprint, orchestrateGoal } from "@/lib/moa"; + +export async function GET() { + return NextResponse.json({ + ...getAgentBlueprint(), + orchestration: orchestrateGoal({ + goal: "Stabilize and expand MindReply through the full MOA-controlled specialist layer.", + }), + }); +} diff --git a/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/components/MOAConsole.tsx b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/components/MOAConsole.tsx new file mode 100644 index 0000000..6f0c185 --- /dev/null +++ b/archive/branch-cleanup-2026-06-09/codex_mindreply-moa-controller/components/MOAConsole.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { useMemo, useState } from "react"; +import { AlertTriangle, Bot, CheckCircle2, Copy, Loader2, Network, Play, ShieldCheck } from "lucide-react"; +import type { OrchestratedGoal } from "@/lib/moa"; + +type MOAConsoleProps = { + initialResult: OrchestratedGoal; +}; + +const DEFAULT_GOAL = + "Expand MindReply with secure AI orchestration, visible platform functionality, automation, integrations, and growth support."; + +function ResponseBlock({ children }: { children: string }) { + return ( +
+ {children} +
+ ); +} + +export default function MOAConsole({ initialResult }: MOAConsoleProps) { + const [goal, setGoal] = useState(initialResult.userGoal || DEFAULT_GOAL); + const [result, setResult] = useState(initialResult); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [copied, setCopied] = useState(false); + + const routingStats = useMemo(() => { + const primary = result.taskRoutingLayer.filter((agent) => agent.priority === "primary").length; + const support = result.taskRoutingLayer.filter((agent) => agent.priority === "support").length; + const observer = result.taskRoutingLayer.filter((agent) => agent.priority === "observer").length; + + return { primary, support, observer }; + }, [result]); + + async function runOrchestration() { + const trimmed = goal.trim(); + if (!trimmed) { + setError("Enter a goal before running the orchestrator."); + return; + } + + setLoading(true); + setError(""); + + try { + const response = await fetch("/api/agent/orchestrate", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ goal: trimmed }), + }); + const payload = await response.json(); + + if (!response.ok) { + throw new Error(payload.error || "Unable to run orchestration."); + } + + setResult(payload); + } catch (err) { + setError(err instanceof Error ? err.message : "Unable to run orchestration."); + } finally { + setLoading(false); + } + } + + async function copyResult() { + const text = [ + result.finalCombinedResult.summary, + "", + "Next actions:", + ...result.finalCombinedResult.nextActions.map((action) => `- ${action}`), + ].join("\n"); + + await navigator.clipboard.writeText(text); + setCopied(true); + window.setTimeout(() => setCopied(false), 1500); + } + + return ( +
+
+
+
+ +
+
+

MOA Console

+

User goal intake and live task routing

+
+
+ + +