Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4276401
Remove louis@mediar.ai from default cost alert recipients
m13v Jun 4, 2026
c815e20
Remove louis@mediar.ai from LLM discrepancy alert recipients
m13v Jun 4, 2026
0f3a7c0
Remove redundant email check from VM provision route
m13v Jun 4, 2026
dbd9bdf
Remove louis@mediar.ai from excluded stats emails
m13v Jun 4, 2026
06d6fa0
Remove louis030195 as dependabot reviewer
m13v Jun 4, 2026
f3855ab
Update release script comments to generalize team member reference
m13v Jun 4, 2026
735b1bc
Remove mock member from admin organization members API
m13v Jun 4, 2026
15f1d22
Remove Louis from legacy Mediar org members list
m13v Jun 4, 2026
a07cf6b
Remove Louis from legacy Mediar org members fallback
m13v Jun 4, 2026
56e6e8c
Remove Louis from notification email reply-to and footer
m13v Jun 4, 2026
d8616ec
Remove Louis Beaumont from legacy organization members
m13v Jun 4, 2026
a239464
Remove louis@mediar.ai from MEDIAR_ADMINS
m13v Jun 4, 2026
e9ac2ad
Remove louis@mediar.ai from cost alert recipients and reply-to
m13v Jun 4, 2026
faab4c2
Remove Louis from VM provisioning email reply-to list
m13v Jun 4, 2026
b440176
Remove Louis from legacy Mediar notification recipients
m13v Jun 4, 2026
ceddd21
Update hardcoded test paths to use generic testuser
m13v Jun 4, 2026
bd44f18
Remove louis@mediar.ai from admin settings email recipients
m13v Jun 4, 2026
e63fcbb
Add backup for deleted issue 45
m13v Jun 4, 2026
135cf1a
docs: write real README with project overview, structure, and setup
m13v Jun 4, 2026
5369c4b
Add backup for deleted issue 78
m13v Jun 4, 2026
851abc7
Add backup for deleted issue #59
m13v Jun 4, 2026
7fc8cd2
chore: remove issue backups from tracking, gitignore the dir
m13v Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ updates:
schedule:
interval: "weekly"
open-pull-requests-limit: 10
reviewers:
- "louis030195"
labels:
- "dependencies"
- "security"
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,6 @@ apps/desktop/apps/vm-dashboard/public/*.zip

# internal ops runbooks, not for public repo
.claude/skills/

# Local-only backups of deleted GitHub issues (may contain sensitive content)
scripts/deleted-issues-backup/
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,65 @@
<img width="1024" height="559" alt="image" src="https://github.com/user-attachments/assets/c8a9bc75-7241-4fc6-955b-6cd2166a3f05" />
<img width="1024" height="559" alt="Mediar" src="https://github.com/user-attachments/assets/c8a9bc75-7241-4fc6-955b-6cd2166a3f05" />

# Mediar

Capture a browser workflow once, then run it on demand against cloud VMs. Mediar records what you do in the browser, turns it into a reusable workflow, and executes it headlessly through MCP-driven virtual machines.

Live app: [app.mediar.ai](https://app.mediar.ai)

## Monorepo layout

| Path | What it is |
|------|------------|
| `apps/web` | Next.js 15 web app (dashboard, auth, billing, workflow management) |
| `apps/desktop` | Tauri desktop app (React + Rust) for capturing workflows |
| `crates/executor` | Rust workflow executor |
| `modal_apps` | Python executor deployed on [Modal](https://modal.com) |
| `packages/infra` | Azure / telemetry utilities |
| `supabase` | Database schema and migrations |

## Architecture

```
GitHub workflows repo → Modal / Rust executor → MCP VMs → Supabase
```

Workflows are stored in a repo, dispatched to the Modal or Rust executor, run on MCP-controlled VMs, and results are persisted to Supabase.

## Quick start

Requires [Bun](https://bun.sh) and Node 20 (see `.nvmrc`).

```bash
bun install
cp .env.example .env.local # fill in your own keys
bun run dev # run all packages
```

Useful scripts:

```bash
bun run dev:web # web app only
bun run dev:desktop # desktop app only (Tauri)
bun run build # build everything
bun run type-check # typecheck all packages
bun run test # run all tests
```

## Deploy

```bash
# Python executor (Modal)
export PYTHONIOENCODING=utf-8 && modal deploy modal_apps/workflow_executor.py

# Rust executor
bun run deploy:rust # dev
bun run deploy:rust:prod # prod
```

## Contributing

Issues and pull requests are welcome. Run `bun run type-check` and `bun run test` before opening a PR.

## License

[MIT](./LICENSE)
2 changes: 1 addition & 1 deletion apps/desktop/scripts/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* ============================================
*
* 1. SSL.com Credentials (~\SSL.com\.env)
* - Get the .env file from Louis or another team member
* - Get the .env file from a team member
* - Place at: C:\Users\<you>\SSL.com\.env
* - Or set MEDIAR_SSL_DIR env var to custom location
* - Required variables:
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/admin/cost-alerts/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export async function GET() {
// Return existing settings or defaults
const settings: CostAlertSettings = data?.value || {
thresholds: DEFAULT_THRESHOLDS,
emailRecipients: ['matt@mediar.ai', 'louis@mediar.ai'],
emailRecipients: ['matt@mediar.ai'],
enabled: true,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,9 @@ export async function GET(
organization: {
id: targetOrgId,
name: 'Mediar (Legacy/Dev)',
membersCount: 2
membersCount: 1
},
members: [
{
id: 'legacy-1',
userId: 'mediar-admin-1',
email: 'louis@mediar.ai',
firstName: 'Louis',
lastName: 'Beaumont',
role: 'org:admin',
createdAt: new Date().toISOString()
},
{
id: 'legacy-2',
userId: 'mediar-admin-2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ async function sendDiscrepancyAlert(details: {
return;
}

const recipients = ['matt@mediar.ai', 'louis@mediar.ai'];
const recipients = ['matt@mediar.ai'];
const subject = `LLM Token Discrepancy: API ${(details.googleTokens/1e6).toFixed(1)}M vs Traced ${(details.dbTokens/1e6).toFixed(1)}M (${details.discrepancyPercent.toFixed(1)}% diff)`;
const body = `
LLM token usage reconciliation detected a significant discrepancy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ async function getOrganizationMembers(orgId: string): Promise<string[]> {
try {
// Handle legacy Mediar org (members not in Clerk; fall back to staff).
if (isLegacyOrg(orgId)) {
return ['louis@mediar.ai', 'matt@mediar.ai'];
return ['matt@mediar.ai'];
}

const clerk = await clerkClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function getOrganizationMembers(orgId: string): Promise<string[]> {

// Handle legacy Mediar org (members not in Clerk; fall back to staff).
if (isLegacyOrg(orgId)) {
return ['louis@mediar.ai', 'matt@mediar.ai'];
return ['matt@mediar.ai'];
}

const clerk = await clerkClient();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function POST(request: NextRequest) {
// Send email using Resend
const { data, error } = await resend.emails.send({
from: `Mediar.ai <${fromEmail}>`,
replyTo: ['matt@mediar.ai', 'louis@mediar.ai'],
replyTo: ['matt@mediar.ai'],
to: Array.isArray(to) ? to : [to],
subject: enhancedSubject,
html: emailHtml,
Expand Down Expand Up @@ -476,7 +476,7 @@ function generateEmailHTML(alert: any, _config: any): string {

<div class="footer">
<p style="margin: 0 0 16px 0; font-size: 14px; color: #666;">
Need help? Contact us at <a href="mailto:matt@mediar.ai" style="color: #000;">matt@mediar.ai</a> or <a href="mailto:louis@mediar.ai" style="color: #000;">louis@mediar.ai</a>
Need help? Contact us at <a href="mailto:matt@mediar.ai" style="color: #000;">matt@mediar.ai</a>
</p>
<p style="margin: 0 0 8px 0; font-size: 12px;">
<a href="${baseUrl}/dashboard" style="color: #666; text-decoration: none;">View Dashboard</a> •
Expand Down
8 changes: 0 additions & 8 deletions apps/web/src/app/api/organization-members/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export async function GET(request: NextRequest) {
// Handle legacy org
if (isLegacyOrg(currentOrgId)) {
allMembers.push(
{ userId: 'legacy-1', email: 'louis@mediar.ai', firstName: 'Louis', lastName: 'Beaumont', role: 'org:admin' },
{ userId: 'legacy-2', email: 'matt@mediar.ai', firstName: 'Matt', lastName: '', role: 'org:admin' }
);
continue;
Expand Down Expand Up @@ -97,13 +96,6 @@ export async function GET(request: NextRequest) {
if (isLegacyOrg(orgId)) {
return NextResponse.json({
members: [
{
userId: 'legacy-1',
email: 'louis@mediar.ai',
firstName: 'Louis',
lastName: 'Beaumont',
role: 'org:admin'
},
{
userId: 'legacy-2',
email: 'matt@mediar.ai',
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/app/api/vm/provision/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,6 @@ export async function POST(request: NextRequest) {

// Check if user is Mediar team (bypass rate limits)
const isMediarUser =
email === 'louis@mediar.ai' ||
email === 'matt@mediar.ai' ||
email?.endsWith('@mediar.ai');

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/app/api/webhooks/clerk/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { encryptSecret } from '@/lib/crypto';
import { randomBytes } from 'crypto';
import { MEDIAR_ORG_IDS } from '@/lib/client-config';

const MEDIAR_ADMINS = ['louis@mediar.ai', 'matt@mediar.ai'];
const MEDIAR_ADMINS = ['matt@mediar.ai'];
const ORG_TOKEN_EXPIRY_DAYS = 365; // 1 year expiry for org tokens

/**
Expand Down
1 change: 0 additions & 1 deletion apps/web/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export const MEDIAR_ORG_IDS = parseList(process.env.NEXT_PUBLIC_MEDIAR_ORG_IDS);
// so non-Mediar addresses are not committed to the repo.
export const EXCLUDED_EMAILS_FROM_STATS = [
'matt@mediar.ai',
'louis@mediar.ai',
'dev@mediar.ai',
...parseList(process.env.EXCLUDED_STATS_EMAILS),
];
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/lib/inngest/functions/cost-alerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const SETTINGS_KEY = 'cost_alerts_config';

const DEFAULT_SETTINGS: CostAlertSettings = {
thresholds: { warning: 500, critical: 800, maximum: 1000 },
emailRecipients: ['matt@mediar.ai', 'louis@mediar.ai'],
emailRecipients: ['matt@mediar.ai'],
enabled: true,
};

Expand Down Expand Up @@ -118,7 +118,7 @@ async function sendAlertEmail(

const { error } = await resend.emails.send({
from: `Mediar Alerts <${fromEmail}>`,
replyTo: ['matt@mediar.ai', 'louis@mediar.ai'],
replyTo: ['matt@mediar.ai'],
to: recipients,
subject: `${config.emoji} Cloud Cost Alert: ${level.toUpperCase()} - $${amount.toLocaleString()}/month`,
html: `
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/inngest/functions/provision-vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ export const provisionVmFunction = inngest.createFunction(

const { data, error } = await resend.emails.send({
from: `Mediar.ai <${fromEmail}>`,
replyTo: ['matt@mediar.ai', 'louis@mediar.ai'],
replyTo: ['matt@mediar.ai'],
to: email,
subject: `🖥️ Your sandbox "${vmName}" is ready!`,
html: `
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/notification-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ export class NotificationService {
try {
// Handle legacy Mediar org (members not in Clerk; fall back to staff).
if (isLegacyOrg(orgId)) {
return ['louis@mediar.ai', 'matt@mediar.ai'];
return ['matt@mediar.ai'];
}

const clerk = await clerkClient();
Expand Down
8 changes: 4 additions & 4 deletions crates/executor/tests/test_end_to_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ async fn test_run_command_then_execute_workflow() {
let mut check_args = Map::new();
check_args.insert(
"run".to_string(),
Value::String(r#"if (Test-Path 'C:\Users\louis\Documents\test-workflow') { 'exists' } else { 'missing' }"#.to_string()),
Value::String(r#"if (Test-Path 'C:\Users\testuser\Documents\test-workflow') { 'exists' } else { 'missing' }"#.to_string()),
);
check_args.insert("shell".to_string(), Value::String("powershell".to_string()));

Expand Down Expand Up @@ -56,7 +56,7 @@ async fn test_run_command_then_execute_workflow() {
list_args.insert(
"run".to_string(),
Value::String(
r#"Get-ChildItem 'C:\Users\louis\Documents\test-workflow' | Select-Object Name"#
r#"Get-ChildItem 'C:\Users\testuser\Documents\test-workflow' | Select-Object Name"#
.to_string(),
),
);
Expand Down Expand Up @@ -88,7 +88,7 @@ async fn test_run_command_then_execute_workflow() {
let mut exec_args = Map::new();
exec_args.insert(
"url".to_string(),
Value::String("file://C:/Users/louis/Documents/test-workflow".to_string()),
Value::String("file://C:/Users/testuser/Documents/test-workflow".to_string()),
);
exec_args.insert("include_detailed_results".to_string(), Value::Bool(true));
exec_args.insert("stop_on_error".to_string(), Value::Bool(true));
Expand Down Expand Up @@ -144,7 +144,7 @@ async fn test_execute_sequence_only() {
let mut args = Map::new();
args.insert(
"url".to_string(),
Value::String("file://C:/Users/louis/Documents/test-workflow".to_string()),
Value::String("file://C:/Users/testuser/Documents/test-workflow".to_string()),
);
args.insert("include_detailed_results".to_string(), Value::Bool(true));
args.insert("inputs".to_string(), json!({"testInput": "hello"}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ VALUES (
"critical": 800,
"maximum": 1000
},
"emailRecipients": ["matt@mediar.ai", "louis@mediar.ai"],
"emailRecipients": ["matt@mediar.ai"],
"enabled": true
}'::jsonb,
NOW(),
Expand Down
Loading