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
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ apps/site/.twoslash
apps/mobile/.expo
apps/mobile/ios
apps/mobile/android
apps/expo-kloq-example/.expo
apps/expo-kloq-example/ios
apps/expo-kloq-example/android
out
# The generated SDKs (sdks/*): no image needs them.
sdks
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ webhooks, notification emails and contacts, optionally after the submitter confi
`AGPL-3.0-only` (root `LICENSE`), except `packages/sdk` (MIT, its own `LICENSE`: customers bundle it), `packages/mcp`
and `packages/cli` (MIT, published with the SDK), the Claude Code plugin and Agent Skill in `plugins/coss` (MIT, its
own `LICENSE`: installed into customers' agents), the framework drivers in `integrations/*` (MIT, each with its own
`LICENSE`), the generated SDKs in `sdks/*` (MIT, each with its own `LICENSE`), `packages/kloq` (MIT, vendored) and
`LICENSE`), the generated SDKs in `sdks/*` (MIT, each with its own `LICENSE`), `packages/kloq` (MIT, vendored), `packages/expo-kloq` (MIT, its own `LICENSE`: kloq's port) and
the fonts (OFL). New code is AGPL unless it lives in one of those; never copy AGPL code into the SDK, the MCP server,
the CLI, the plugin, the drivers or the generated SDKs. Outside contributors sign the CLA (`CLA.md`, enforced by cla-assistant.io on pull requests; the agreement
text it shows is a gist that must match `CLA.md`: change both together, as a new version).
Expand All @@ -41,6 +41,7 @@ text it shows is a gist that must match `CLA.md`: change both together, as a new
| `apps/sign` | `@coss/sign-app` | Sign's signing page (:3004) on its own domain (`cosssign.com`, CloudFront → ALB, `SIGN_HOST` in `infra/aws/routing.ts`): one signer, signed in by a session the customer's backend creates (token in the URL fragment, never a cookie), embedded with `coss/sign`'s `mountSigning` or opened on its own; consent, the documents (pdf.js), fields, a typed or drawn signature, decline; and the embedded template builder (`/b/<id>`). See `apps/sign/AGENTS.md`. |
| `apps/scheduling` | `@coss/scheduling-app` | The hosted scheduling pages (:3005) on a domain of their own (production: `cossbook.com`, CloudFront → ALB, `SCHEDULING_HOST` in `infra/aws/routing.ts`): a host's page, the booker and the manage page, each signed in by a session (token in the URL fragment, never a cookie), embedded with `coss/scheduling`. A thin client of the API's `/scheduling/client/*`. See `apps/scheduling/AGENTS.md`. |
| `apps/mobile` | `@coss/mobile` | Expo (SDK 57) iOS companion app, three native tabs (Home, Products, Organization), mirroring the dashboard's pages (organization: projects, reputation, domains with DMARC, dedicated IPs, SMS senders, members, spend and budgets, settings with SSO and SCIM, billing (read-only), audit log; environment: emails, templates, received, suppressions, tracking, content storage, SMS, broadcasts, contacts, audiences, segments, topics, marketing settings, risk with its rules, lists and settings, Hooks, Scheduling, API keys, webhooks, logs, log drains), mostly read-only, signed in through the dashboard (browser hand-off + PKCE) and using its `/api/mobile/v1/*`, with push notifications (APNs) for alerts, crash reporting (Sentry) and account deletion. Not deployed by `infra`. See `apps/mobile/AGENTS.md`. |
| `apps/expo-kloq-example` | `@coss/expo-kloq-example` | Expo (SDK 57) development build showing `expo-kloq` with seeded calendars; its `plugins/with-scene-delegate.js` adopts the scene life cycle iOS 27 requires, and `maestro/demo.yaml` walks every interaction. Not deployed. See its `README.md`. |
| `apps/smtp` | `@coss/smtp` | Bun SMTP relay (`smtp.coss.com`; locally :2465 implicit TLS, :2587 STARTTLS) on `smtp-server`: username `coss`, an API key as the password; each message becomes a `POST /v1/emails` request (`@coss/email/smtp`) stored through the same email service, rate limits and quotas. Behind an NLB; terminates TLS itself (exported ACM certificate). See `apps/smtp/AGENTS.md`. |
| `apps/worker` | `@coss/worker` | Bun background worker: email dispatch (pg-boss), scheduled sends, broadcast fan-out, webhook deliveries, domain re-checks, dedicated IP pools, stale-claim/unclaimed sweeps, data retention purges, organization deletions, SES events and received emails from SQS (`EMAIL_PROVIDER=ses`). `scripts/ses-bootstrap.ts`. Safe to run several. |
| `packages/db` | `@coss/db` | Drizzle schema (generated Better Auth tables + domain tables), pg client, TypeIDs, migrations, test guard; `./jobs` (pg-boss); `./idempotency` (`withIdempotencyKey`, request hashes); `./partitions` (daily range partitions: create ahead, drop expired). |
Expand Down Expand Up @@ -77,7 +78,8 @@ text it shows is a gist that must match `CLA.md`: change both together, as a new
| `packages/mcp` | `coss-mcp` | **Public** MCP server (`npx coss-mcp`, stdio, Node; hosted at `api.coss.com/mcp` by `apps/api` through `coss-mcp/http`): tools for coding agents built only on the published `coss` SDK: docs search, per-product setup (`get_setup`) and diagnosis (`diagnose_*`), sends, suppressions, domains, webhooks, received mail, logs, log drains, marketing, sub-accounts, Verify, Sign, Forms, Scheduling, Hooks, Risk, account status, a read-only fallback for any `/v1/` path (`api_get`), plus prompts and resources; `COSS_MCP_TOOLSETS` offers fewer. See `packages/mcp/AGENTS.md`. |
| `plugins/coss` | — | **Public** Claude Code plugin (marketplace: the root `.claude-plugin/marketplace.json`; `/plugin install coss --marketplace cosscom/coss`) with the `coss` Agent Skill (`skills/coss/SKILL.md`, also installed into other agents by `npx skills add cosscom/coss`) and the MCP server (`.mcp.json`, `npx -y coss-mcp`). Plain files, not a workspace; served from main. See "Coding agents" below. |
| `packages/cli` | `coss-cli` | **Public** CLI (`npm i -g coss-cli`, command `coss`, Node): `login`, `init`, `emails`, `suppressions`, `subaccounts`, `sms`, `verify`, `contacts`, `audiences`, `segments`, `topics`, `broadcasts`, `marketing settings`, `sign`, `forms`, `scheduling`, `hooks`, `risk`, `domains`, `templates`, `webhooks`, `logs tail`, `log-drains`, `listen`, `api`, built only on the published `coss` SDK. See `packages/cli/AGENTS.md`. |
| `packages/kloq` | `kloq` | The kloq library (the `cosscom/calendar` repository's `packages/kloq/src`, tests left out): the `<Kloq>` calendar, its themes, settings dialog, design mode and stylesheet. Vendored (excluded from Biome, `noUncheckedIndexedAccess` off); also what `/kloq/r/kloq.json` serves as source. Used only by `apps/site`. |
| `packages/kloq` | `kloq` | The kloq library (the `cosscom/calendar` repository's `packages/kloq/src`, tests left out): the `<Kloq>` calendar, its themes, settings dialog, design mode and stylesheet. Vendored (excluded from Biome, `noUncheckedIndexedAccess` off); also what `/kloq/r/kloq.json` serves as source. Used by `apps/site`, and by `packages/expo-kloq` through `kloq/lib/*` (the model modules only). |
| `packages/expo-kloq` | `expo-kloq` | kloq for Expo / React Native: `<KloqCalendar>` (day, 3-day, week with a multi-zone gutter, month, year, schedule, timeline; the event sheet with recurrence and its scope dialog, quick add, calendar and zone settings) on kloq's own model, drawn with native controls — SwiftUI via `@expo/ui` (Liquid Glass via `expo-glass-effect`) on iOS, Jetpack Compose on Android. MIT. See `packages/expo-kloq/AGENTS.md`. |
| `tooling/typescript` | `@coss/tsconfig` | Shared tsconfig bases. |
| `tooling/sdkgen` | `@coss/sdkgen` | Builds the generated SDKs: `SDK_METHODS` (every operation's SDK method, named as in the TypeScript SDK) and `NO_RETRY` (`src/methods.ts`), the derive step that turns `apps/api/openapi.json` into the spec Fern generates from (`src/derive.ts`), the post-processing of each generator's output (`src/languages.ts`), the API reference's code samples (`snippets.json`, from each SDK's `reference.md`); `scripts/generate.ts` (`bun run sdks:generate`, needs Docker). See `sdks/AGENTS.md`. |
| `sdks/{python,go,ruby,php,java,dotnet}` | `cosscom` (PyPI, `import coss`), `github.com/cosscom/coss-go`, `coss` (gem), `coss/coss` (Composer), `com.coss:coss` (Maven), `Coss` (NuGet) | **Public** SDKs generated from the OpenAPI document by Fern's open-source generators (`sdks/fern/generators.yml`), each with a few hand-written files kept by its `.fernignore` (webhook verification, tests, metadata). Not workspaces. Regenerated on main by the "Regenerate SDKs" PR; released by `release-sdks.yml`. See `sdks/AGENTS.md`. |
Expand All @@ -95,7 +97,7 @@ billing enqueues `billing.*` events through it — and email imports only `./met
`smtp → tenancy, email, ratelimit, logs, billing, observability, db`;
`web → auth, tenancy, email, sms, sign, forms, support, hooks, reputation, screening, webhooks, ratelimit, versioning, retention, logs, billing, risk, push, migrate, companion, db, ui`; `mobile → companion` (types only; a dev dependency), `retention`, `observability`, `dashboard-routes` (bundled by Metro: keep them dependency-free); `portal → hooks, webhooks, ratelimit, logs, observability, db, ui`; `sign-app → sign, ratelimit, observability, db, ui`; `site → ui, auth, versioning, retention, billing, risk, reputation, ratelimit, sms, sdkgen` (only the client-safe `@coss/auth/{slugs,constants,api-key-permissions}`, the changelog, the retention policy, `@coss/billing/{catalog,money,engine}`, `@coss/risk/schemas`, `@coss/reputation/schemas` (the default policy), `@coss/ratelimit/buckets` (the bucket catalog, no imports), `@coss/sms/segments` (the segment calculator) and `@coss/sdkgen`'s `snippets.json` + `./methods`) +
`@coss/api/openapi.json` (data only, never api code); `sdkgen → @coss/api/openapi.json` (data only); `infra → auth, email, sms, sign, hooks, ratelimit, screening, billing` (`slugs`, `env` only);
`worker → auth (`./emails` only), email, sms, sign, verify, forms, support, hooks, reputation, screening, webhooks, ratelimit, logs, risk, push, migrate, tenancy, retention, billing, db` (from tenancy only `./retention`, `./deletion` and `./logos`, which import nothing but `db` and `signals`); `sdk`, `ui`, `kloq` and `companion` depend on no workspace (`site → kloq`); `mcp` and `cli` depend only on the published `coss` (a
`worker → auth (`./emails` only), email, sms, sign, verify, forms, support, hooks, reputation, screening, webhooks, ratelimit, logs, risk, push, migrate, tenancy, retention, billing, db` (from tenancy only `./retention`, `./deletion` and `./logos`, which import nothing but `db` and `signals`); `sdk`, `ui`, `kloq` and `companion` depend on no workspace (`site → kloq`, `expo-kloq → kloq`, `expo-kloq-example → expo-kloq`); `mcp` and `cli` depend only on the published `coss` (a
semver range, not `workspace:`).
Dev-only: `email`/`sms`/`verify`/`forms`/`webhooks`/`hooks`/`reputation`/`screening`/`ratelimit`/`logs`/`billing`/`risk`/`push`/`notifications`/`migrate`/`worker → tenancy` (test fixtures — so tenancy and auth must never import email, webhooks, screening, ratelimit, logs, billing or notifications (the layout they share is the dependency-free `@coss/email-layout`) (plan limits reach Better Auth through `setMembershipLimitResolver` from `@coss/auth/limits`); Better Auth's own audit events reach the log through `setOrgAuditRecorder` from `@coss/auth/audit`) and
`api`/`worker`/`site → coss` and `api → coss-cli` (SDK, MCP and CLI contract tests and the docs' Twoslash snippets — turbo builds the SDK before their
Expand Down
5 changes: 5 additions & 0 deletions apps/expo-kloq-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Continuous Native Generation: `expo prebuild` writes these from app.json and plugins/
ios/
android/
.expo/
logs/
25 changes: 25 additions & 0 deletions apps/expo-kloq-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# expo-kloq example

A development build (not Expo Go: `@expo/ui` and `expo-glass-effect` are native modules) that shows
`<KloqCalendar>` from `packages/expo-kloq` with four seeded calendars and a few weeks of events placed
relative to today.

```sh
bun install # repo root
bun run --cwd apps/expo-kloq-example prebuild -- -p ios # writes ios/ from app.json + plugins/
bun run --cwd apps/expo-kloq-example ios # build, install, start Metro
```

`ios/` and `android/` are generated (Continuous Native Generation): change `app.json` or
`plugins/` and prebuild again, never edit them. If `pod install` fails with a Unicode
normalization error, run it with `LANG=en_US.UTF-8`.

- **iOS 27** asserts at launch unless the app uses the scene life cycle, which SDK 57's prebuild
template doesn't. `plugins/with-scene-delegate.js` makes the AppDelegate an
`ExpoReactNativeFactoryProvider` and points `UIApplicationSceneManifest` at Expo's
`ExpoAppSceneDelegate`, which starts React Native in the scene's window.
- **Metro** (`metro.config.js`) pins React, React Native and the native modules' JS to this app, so
kloq's sources (whose workspace has the web apps' React) share one React.
- **Demo**: `maestro/demo.yaml` walks every interaction. Run it on a Release build
(`bunx expo run:ios --configuration Release`): a debug build's dev-client button covers the
sheets' Save button.
20 changes: 20 additions & 0 deletions apps/expo-kloq-example/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"expo": {
"name": "kloq",
"slug": "expo-kloq-example",
"version": "1.0.0",
"orientation": "portrait",
"scheme": "expokloq",
"userInterfaceStyle": "automatic",
"platforms": ["ios", "android"],
"ios": {
"bundleIdentifier": "com.coss.kloq.example",
"supportsTablet": true,
"config": { "usesNonExemptEncryption": false }
},
"android": {
"package": "com.coss.kloq.example"
},
"plugins": ["./plugins/with-scene-delegate.js"]
}
}
8 changes: 8 additions & 0 deletions apps/expo-kloq-example/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"root": false,
"$schema": "https://biomejs.dev/schemas/2.5.15/schema.json",
"extends": "//",
"linter": {
"domains": { "react": "recommended" }
}
}
4 changes: 4 additions & 0 deletions apps/expo-kloq-example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { registerRootComponent } from "expo";
import { App } from "./src/app";

registerRootComponent(App);
157 changes: 157 additions & 0 deletions apps/expo-kloq-example/maestro/demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# A paced walkthrough of every interaction, for recording a demo on the iOS Simulator:
# xcrun simctl io booted recordVideo --codec=h264 demo.mov (in another terminal)
# JAVA_HOME=… maestro test maestro/demo.yaml
# Run it against a Release build (`expo run:ios --configuration Release`): a debug build's
# dev-client button sits over the sheets' Save button. Seeds are relative to today.
appId: com.coss.kloq.example
---
- stopApp
- launchApp
- extendedWaitUntil:
visible: "Quick add"
timeout: 30000
- runFlow: pause.yaml

# ── navigation ──────────────────────────────────────────────────────────────
- tapOn: "Next week"
- runFlow: pause.yaml
- tapOn: "Next week"
- runFlow: pause.yaml
- tapOn: "Previous week"
- runFlow: pause.yaml
- tapOn: "Today"
- runFlow: pause.yaml

# ── views ───────────────────────────────────────────────────────────────────
- tapOn: "Day"
- runFlow: pause.yaml
- tapOn: "Next day"
- runFlow: pause.yaml
- tapOn: "Today"
- tapOn: "Month"
- runFlow: pause.yaml
- runFlow: pause.yaml
- tapOn: "Year"
- runFlow: pause.yaml
- scroll
- runFlow: pause.yaml
- tapOn: "Schedule"
- runFlow: pause.yaml
- scroll
- runFlow: pause.yaml
- tapOn: "More"
- tapOn: "View"
- tapOn: "Timeline"
- runFlow: pause.yaml
- runFlow: pause.yaml
- tapOn: "More"
- tapOn: "View"
- tapOn: "3 days"
- runFlow: pause.yaml
- tapOn: "Week"
- runFlow: pause.yaml

# ── the event sheet: native pickers, all-day, calendar, recurrence, alerts ─
- tapOn: "Design review, .*"
- runFlow: pause.yaml
- tapOn: "13:00"
- runFlow: pause.yaml
- swipe:
start: "51%,62%"
end: "51%,58%"
- runFlow: pause.yaml
- tapOn:
point: "50%,12%"
- runFlow: pause.yaml
- tapOn:
point: "57%,45%"
- runFlow: pause.yaml
- runFlow: pause.yaml
- tapOn:
point: "50%,12%"
- tapOn:
point: "84%,38%"
- runFlow: pause.yaml
- tapOn:
point: "84%,38%"
- runFlow: pause.yaml
- tapOn: "Team"
- runFlow: pause.yaml
- tapOn: "Work"
- runFlow: pause.yaml
- tapOn: "Does not repeat"
- runFlow: pause.yaml
- tapOn: "Daily"
- runFlow: pause.yaml
- tapOn: "Daily"
- runFlow: pause.yaml
- tapOn: "Custom…"
- runFlow: pause.yaml
- scroll
- runFlow: pause.yaml
- tapOn: "Weekly"
- runFlow: pause.yaml
- tapOn: "Wednesday"
- runFlow: pause.yaml
- tapOn: "Every week, Increment"
- runFlow: pause.yaml
- tapOn: "Add alert"
- runFlow: pause.yaml
- tapOn: "Save"
- runFlow: pause.yaml

# ── a repeating event: the "this / this and following / all" dialog ──────
- tapOn:
text: "Standup, .*"
index: 2
- runFlow: pause.yaml
- tapOn: "Standup"
- inputText: " (moved)"
- tapOn: "Save"
- runFlow: pause.yaml
- tapOn: "This event"
- runFlow: pause.yaml
- tapOn:
text: "Standup, .*"
index: 3
- runFlow: pause.yaml
- scroll
- tapOn: "Delete event"
- runFlow: pause.yaml
- tapOn: "This and following events"
- runFlow: pause.yaml

# ── new event and quick add ─────────────────────────────────────────────────
- tapOn: "New event"
- runFlow: pause.yaml
- tapOn: "Title"
- inputText: "Focus time"
- runFlow: pause.yaml
- tapOn: "Add"
- runFlow: pause.yaml
- tapOn: "Quick add"
- runFlow: pause.yaml
- inputText: "Coffee with Dana tomorrow 3pm at Monmouth"
- runFlow: pause.yaml
- runFlow: pause.yaml
- tapOn: "Add"
- runFlow: pause.yaml

# ── calendar toggles and the multi-zone gutter ─────────────────────────────
- tapOn: "More"
- tapOn: "Calendars & time zones"
- runFlow: pause.yaml
- tapOn:
point: "84%,29%"
- runFlow: pause.yaml
- tapOn:
point: "84%,77%"
- runFlow: pause.yaml
- tapOn: "Done"
- runFlow: pause.yaml
- tapOn: "Day"
- runFlow: pause.yaml
- runFlow: pause.yaml
- tapOn: "Week"
- runFlow: pause.yaml
- runFlow: pause.yaml
7 changes: 7 additions & 0 deletions apps/expo-kloq-example/maestro/pause.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# A beat between demo steps, so the recording is watchable.
appId: com.coss.kloq.example
---
- extendedWaitUntil:
visible: "__pause__"
timeout: 1800
optional: true
Loading
Loading