Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 44 additions & 5 deletions .github/workflows/cli-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,49 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Output Homebrew formula values
- name: Update Homebrew formula
run: |
echo "::notice::Update Homebrew formula with these SHA256 values:"
echo "darwin-arm64: ${{ steps.sha.outputs.darwin_arm64 }}"
echo "darwin-x64: ${{ steps.sha.outputs.darwin_x64 }}"
echo "linux-x64: ${{ steps.sha.outputs.linux_x64 }}"
python3 << 'EOF'
import re

with open('Formula/inbox-zero.rb', 'r') as f:
content = f.read()

# Update version
content = re.sub(r'version "[^"]*"', 'version "${{ steps.version.outputs.version }}"', content)

# Update SHA256 for darwin-arm64 (first sha256 after "on_arm do")
content = re.sub(
r'(on_arm do.*?sha256 ")[^"]*(")',
r'\g<1>${{ steps.sha.outputs.darwin_arm64 }}\2',
content, count=1, flags=re.DOTALL
)

# Update SHA256 for darwin-x64 (first sha256 after "on_intel do" inside on_macos)
content = re.sub(
r'(on_macos do.*?on_intel do.*?sha256 ")[^"]*(")',
r'\g<1>${{ steps.sha.outputs.darwin_x64 }}\2',
content, count=1, flags=re.DOTALL
)

# Update SHA256 for linux-x64 (sha256 after "on_linux do")
content = re.sub(
r'(on_linux do.*?sha256 ")[^"]*(")',
r'\g<1>${{ steps.sha.outputs.linux_x64 }}\2',
content, count=1, flags=re.DOTALL
)

with open('Formula/inbox-zero.rb', 'w') as f:
f.write(content)

print(content)
EOF

- name: Commit formula update
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add Formula/inbox-zero.rb
git diff --staged --quiet || git commit -m "chore: update Homebrew formula for CLI v${{ steps.version.outputs.version }}"
git push origin HEAD:${{ github.ref_name }}

4 changes: 2 additions & 2 deletions Formula/inbox-zero.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

class InboxZero < Formula
desc "CLI tool for setting up Inbox Zero - AI email assistant"
homepage "https://getinboxzero.com"
version "2.21.15"
homepage "https://www.getinboxzero.com"
version "2.21.16"
license "AGPL-3.0-only"

on_macos do
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ To request a feature open a [GitHub issue](https://github.com/elie222/inbox-zero

## Getting Started

We offer a hosted version of Inbox Zero at [https://getinboxzero.com](https://getinboxzero.com).
We offer a hosted version of Inbox Zero at [https://getinboxzero.com](https://www.getinboxzero.com).

### Self-Hosting with Docker

Expand Down Expand Up @@ -221,7 +221,9 @@ Go to [Microsoft Azure Portal](https://portal.azure.com/) and create a new Azure
3. Click "New registration"

1. Choose a name for your application
2. Under "Supported account types" select "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)"
2. Under "Supported account types" select one of:
- **Multitenant (default):** "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)" - allows any Microsoft account
- **Single tenant:** "Accounts in this organizational directory only" - restricts to your organization only
3. Set the Redirect URI:
- Platform: Web
- URL: `http://localhost:3000/api/auth/callback/microsoft` (replace `localhost:3000` with your domain in production)
Expand All @@ -234,7 +236,8 @@ Go to [Microsoft Azure Portal](https://portal.azure.com/) and create a new Azure
4. Get your credentials from the `Overview` tab:

1. Copy the "Application (client) ID" → this is your `MICROSOFT_CLIENT_ID`
2. Go to "Certificates & secrets" in the left sidebar
2. If using single tenant, copy the "Directory (tenant) ID" → this is your `MICROSOFT_TENANT_ID`
3. Go to "Certificates & secrets" in the left sidebar
- Click "New client secret"
- Add a description and choose an expiry
- Click "Add"
Expand Down Expand Up @@ -266,6 +269,7 @@ Go to [Microsoft Azure Portal](https://portal.azure.com/) and create a new Azure
```
MICROSOFT_CLIENT_ID=your_client_id_here
MICROSOFT_CLIENT_SECRET=your_client_secret_here
MICROSOFT_TENANT_ID=your_tenant_id_here # Only needed for single tenant, omit for multitenant
```

### LLM Setup
Expand Down
182 changes: 182 additions & 0 deletions apps/web/app/(app)/admin/config/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import fs from "node:fs";
import path from "node:path";
import { env } from "@/env";
import { auth } from "@/utils/auth";
import { isAdmin } from "@/utils/admin";
import { ErrorPage } from "@/components/ErrorPage";
import { PageWrapper } from "@/components/PageWrapper";
import { PageHeader } from "@/components/PageHeader";

export default async function AdminConfigPage() {
const session = await auth();

if (!isAdmin({ email: session?.user.email })) {
return (
<ErrorPage
title="No Access"
description="You do not have permission to access this page."
/>
);
}

const version = getVersion();

const info = {
version,
environment: process.env.NODE_ENV,
baseUrl: env.NEXT_PUBLIC_BASE_URL,
features: {
emailSendEnabled: env.NEXT_PUBLIC_EMAIL_SEND_ENABLED,
contactsEnabled: env.NEXT_PUBLIC_CONTACTS_ENABLED,
bypassPremiumChecks: env.NEXT_PUBLIC_BYPASS_PREMIUM_CHECKS ?? false,
},
providers: {
google: !!env.GOOGLE_CLIENT_ID,
microsoft: !!env.MICROSOFT_CLIENT_ID,
microsoftTenantConfigured:
!!env.MICROSOFT_TENANT_ID && env.MICROSOFT_TENANT_ID !== "common",
},
llm: {
defaultProvider: env.DEFAULT_LLM_PROVIDER,
defaultModel: env.DEFAULT_LLM_MODEL ?? "default",
economyProvider: env.ECONOMY_LLM_PROVIDER ?? "not configured",
economyModel: env.ECONOMY_LLM_MODEL ?? "not configured",
},
integrations: {
redis: !!env.UPSTASH_REDIS_URL || !!env.REDIS_URL,
qstash: !!env.QSTASH_TOKEN,
tinybird: !!env.TINYBIRD_TOKEN,
sentry: !!env.NEXT_PUBLIC_SENTRY_DSN,
posthog: !!env.NEXT_PUBLIC_POSTHOG_KEY,
stripe: !!env.STRIPE_SECRET_KEY,
lemonSqueezy: !!env.LEMON_SQUEEZY_API_KEY,
},
};

return (
<PageWrapper className="max-w-2xl mx-auto">
<PageHeader title="App Configuration" />

<div className="space-y-4 mt-4">
<Section title="Application">
<Row label="Version" value={info.version} />
<Row label="Environment" value={info.environment} />
<Row label="Base URL" value={info.baseUrl} />
</Section>

<Section title="Features">
<Row
label="Email Send"
value={info.features.emailSendEnabled ? "Enabled" : "Disabled"}
/>
<Row
label="Contacts"
value={info.features.contactsEnabled ? "Enabled" : "Disabled"}
/>
<Row
label="Bypass Premium"
value={info.features.bypassPremiumChecks ? "Yes" : "No"}
/>
</Section>

<Section title="Auth Providers">
<Row
label="Google"
value={info.providers.google ? "Configured" : "Not configured"}
/>
<Row
label="Microsoft"
value={info.providers.microsoft ? "Configured" : "Not configured"}
/>
<Row
label="Microsoft Tenant"
value={
info.providers.microsoftTenantConfigured
? "Single tenant"
: "Multitenant (common)"
}
/>
</Section>

<Section title="LLM Configuration">
<Row label="Default Provider" value={info.llm.defaultProvider} />
<Row label="Default Model" value={info.llm.defaultModel} />
<Row label="Economy Provider" value={info.llm.economyProvider} />
<Row label="Economy Model" value={info.llm.economyModel} />
</Section>

<Section title="Integrations">
<Row
label="Redis"
value={info.integrations.redis ? "Configured" : "Not configured"}
/>
<Row
label="QStash"
value={info.integrations.qstash ? "Configured" : "Not configured"}
/>
<Row
label="Tinybird"
value={info.integrations.tinybird ? "Configured" : "Not configured"}
/>
<Row
label="Sentry"
value={info.integrations.sentry ? "Configured" : "Not configured"}
/>
<Row
label="PostHog"
value={info.integrations.posthog ? "Configured" : "Not configured"}
/>
<Row
label="Stripe"
value={info.integrations.stripe ? "Configured" : "Not configured"}
/>
<Row
label="Lemon Squeezy"
value={
info.integrations.lemonSqueezy ? "Configured" : "Not configured"
}
/>
</Section>
</div>
</PageWrapper>
);
}

function Section({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border border-slate-200 bg-white">
<h2 className="border-b border-slate-200 px-4 py-3 font-semibold text-slate-900">
{title}
</h2>
<div className="divide-y divide-slate-100">{children}</div>
</div>
);
}

function Row({ label, value }: { label: string; value: string | boolean }) {
const displayValue =
typeof value === "boolean" ? (value ? "Yes" : "No") : value;

return (
<div className="flex justify-between px-4 py-2">
<span className="text-slate-600">{label}</span>
<span className="font-mono text-sm text-slate-900">{displayValue}</span>
</div>
);
}

// Read version at build time
function getVersion(): string {
try {
const versionPath = path.join(process.cwd(), "../../version.txt");
return fs.readFileSync(versionPath, "utf-8").trim();
} catch {
return "unknown";
}
}
53 changes: 39 additions & 14 deletions apps/web/app/(landing)/login/error/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import Link from "next/link";
import { Suspense, useEffect } from "react";
import { Button } from "@/components/ui/button";
Expand All @@ -14,9 +14,19 @@ import { WELCOME_PATH } from "@/utils/config";
import { CrispChatLoggedOutVisible } from "@/components/CrispChat";
import { getAndClearAuthErrorCookie } from "@/utils/auth-cookies";

export default function LogInErrorPage() {
const errorMessages: Record<string, { title: string; description: string }> = {
email_not_found: {
title: "Account Not Authorized",
description:
"Your account is not authorized to access this application. This may be because your email is not part of the allowed organization. Please contact your administrator or try signing in with a different account.",
},
};

function LoginErrorContent() {
const { data, isLoading, error } = useUser();
const router = useRouter();
const searchParams = useSearchParams();
const errorCode = searchParams.get("error");

// For some reason users are being sent to this page when logged in
// This will redirect them out of this page to the app
Expand All @@ -36,20 +46,35 @@ export default function LogInErrorPage() {
// will redirect to welcome if user is logged in
if (data?.id) return <Loading />;

const errorInfo = errorCode ? errorMessages[errorCode] : null;
const title = errorInfo?.title || "Error Logging In";
const supportText = `If this error persists, please use the support chat or email us at ${env.NEXT_PUBLIC_SUPPORT_EMAIL}.`;
const description = errorInfo?.description
? `${errorInfo.description} ${supportText}`
: `Please try again. ${supportText}`;

return (
<LoadingContent loading={isLoading} error={error}>
<ErrorPage
title={title}
description={description}
button={
<Button asChild>
<Link href="/login">Log In</Link>
</Button>
}
/>
{/* <AutoLogOut loggedIn={!!session?.user.email} /> */}
</LoadingContent>
);
}

export default function LogInErrorPage() {
return (
<BasicLayout>
<LoadingContent loading={isLoading} error={error}>
<ErrorPage
title="Error Logging In"
description={`Please try again. If this error persists, please use the support chat or email us at ${env.NEXT_PUBLIC_SUPPORT_EMAIL}.`}
button={
<Button asChild>
<Link href="/login">Log In</Link>
</Button>
}
/>
{/* <AutoLogOut loggedIn={!!session?.user.email} /> */}
</LoadingContent>
<Suspense fallback={<Loading />}>
<LoginErrorContent />
</Suspense>

<Suspense>
<CrispChatLoggedOutVisible />
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/(marketing)
Submodule (marketing) updated from feaad5 to 5adac6
2 changes: 1 addition & 1 deletion apps/web/app/api/v1/openapi/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export async function GET(request: NextRequest) {
? [{ url: `${customHost}/api/v1`, description: "Custom host" }]
: []),
{
url: "https://getinboxzero.com/api/v1",
url: "https://www.getinboxzero.com/api/v1",
description: "Production server",
},
{ url: "http://localhost:3000/api/v1", description: "Local development" },
Expand Down
2 changes: 1 addition & 1 deletion copilot/inbox-zero-ecs/manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ network:

variables: # Pass environment variables as key value pairs.
HOSTNAME: 0.0.0.0
NEXT_PUBLIC_BASE_URL: # YOUR_DOMAIN, e.g. https://getinboxzero.com (with http or https)
NEXT_PUBLIC_BASE_URL: # YOUR_DOMAIN, e.g. https://www.getinboxzero.com (with http or https)
DEFAULT_LLM_PROVIDER:

# Set these secrets at AWS Systems Manager (SSM) Parameter Store for extra security.
Expand Down
Loading
Loading