DeepSeek Harness Remote
DeepSeek Harness Remote: DeepSeek Harness 실행 PC와 모바일·PC를 안전하게 연결해 어디서나 작업 이어받기
오픈소스 일정 관리 소프트웨어, Calendly 대안
Open-source scheduling software, an alternative to Calendly: share a booking link, let invitees pick a time, and let AI assistants book through an API and a remote MCP server. Hourmere Cloud is in an
생산성TypeScript · AstroGitHub @hourmere온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Open-source, agent-native scheduling: an AGPL Calendly alternative with a REST API and an MCP server. Self-host it; Hourmere Cloud is in invitation-only preview.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Open-source scheduling for people and AI agents. Self-host from source; Hourmere Cloud is coming.
Early development. Nothing here is ready to use yet.
| Path | What it is |
|---|---|
apps/server | API server (Hono). Serves the built web app in production. |
apps/web | Web app (React + Vite). Talks to the server only through the public API. |
apps/site | Website for hourmere.com (Astro + Starlight): homepage, legal pages, docs. Static; not part of the Docker image. |
packages/core | Scheduling logic with no I/O or framework code, such as the availability engine. |
packages/db | Database access (Kysely) and SQL migrations in packages/db/migrations. |
packages/scheduling | Service layer shared by the REST API and the MCP server: reads and writes the database, enforces permissions, throws DomainError. |
packages/auth | Sign-in (Better Auth: email links, Google, Microsoft), sessions, organizations, API keys. |
packages/calendars | Separate Google Calendar OAuth, encrypted credentials, incremental busy-time sync and booking writeback. See Google Calendar. |
packages/conferences | Independent Zoom connections and single-host automatic meetings; see automatic Zoom, separate from sign-in. |
packages/mail | Outgoing email: SMTP, or the server log when SMTP is not configured; booking email templates and calendar invitations. |
packages/jobs | Background jobs on Postgres (pg-boss): typed job definitions, enqueueing inside a transaction, workers. |
packages/webhooks | Optional signed organization booking webhooks, transactional delivery and bounded retries. See webhooks. |
packages/notifications | Durable booking and sign-in email delivery, retries and the encrypted credential outbox. See notifications. |
tools/api-types | Generates the web app's API types from the server's OpenAPI document (npm run api:types). |
tools/cli | API-key command-line client using the existing MCP tools (npm run --silent cli -- --help). See CLI. |
Requires Node.js 26 and Docker.
cp .env.example .env # set BETTER_AUTH_SECRET, e.g. `openssl rand -base64 32`
npm install
npm run db:up # Postgres on localhost:5432
npm run dev # API on :3000, web app on http://localhost:5173
Without SMTP_URL, sign-in links are printed in the npm run dev output instead of being emailed.
In development anyone may sign up unless SIGNUP or SIGNUP_ALLOWED_DOMAINS is set (see Self-hosting).
Open the app at http://localhost:5173. Vite serves it and proxies /api/ and /sign-in/confirm (the server's page for emailed sign-in links) to the API server (on PORT from the environment or .env, default 3000), so the browser talks to one origin, as in production. To run the two separately, start the API with sign-in links and cookies pointing at Vite (and open sign-up, as npm run dev does), then Vite:
PUBLIC_URL=http://localhost:5173 SIGNUP=open npm run dev --workspace @hourmere/server
npm run dev --workspace @hourmere/web
Signed-in pages live under /app (/app/event-types, /app/availability, /app/bookings, /app/api-keys, /app/settings); /sign-in stays at the top level, and the rest of the root is for public booking pages (/{handle}/{slug} for personal types and
/team/{teamSlug}/{slug} for individual, collective and activated round-robin team types). After signing in, people only return to pages under /app.
One-time booking links open /book/{token}. They offer the usual booking form,
including editable invitee prefill, and are consumed only when a booking commits.
Every unavailable link shows the same neutral page. These links cannot be embedded;
their pages and API responses send no referrer and are not cached. Create, list and
revoke them in the event editor, REST, MCP or CLI; the full URL is shown only in the
creation response. See one-time booking links
for permissions, replay handling and limits.
Saving an event type opens its saved version's View and share actions. Open the public booking page or copy its full link there; unsaved address changes cannot be copied as a published link. When setup or enablement needs attention, the panel links to the relevant settings. Round-robin pools still require a separate explicit activation after saving.
When the operator enables SLOT_SNIPPETS_ENABLED (off by default), saved event views
let personal owners and team owners/admins pick 1–10 open alternatives for one meeting
length and copy a plain-text or rich-text email snippet. The picker checks ordinary
public availability again before every copy; no time is reserved. Hidden event links
can be forwarded. Enable this setting only after public direct-slot booking pages are
deployed on the same instance. Email language and display zone affect labels only;
the booking page keeps the invitee’s own preferences.
The web app only uses the public API (/api/v1/* and Better Auth's /api/auth/*). Its types for /api/v1 are generated from the server's OpenAPI document: after changing a route, run npm run api:types (no database needed) and commit apps/web/src/api/schema.d.ts; CI fails if it is out of date.
Other commands: npm run lint, npm run typecheck, npm test, npm run build, npm run db:migrate, npm run db:down, npm run api:types.
The website runs on its own: npm run dev --workspace @hourmere/site (http://localhost:4321). npm run build writes it to apps/site/dist. A small Cloudflare Worker handles the Hourmere Cloud waitlist and serves the static assets on hourmere.com and www.hourmere.com (config in apps/site/wrangler.jsonc). The D1 database already exists. See website operations for local D1/form testing and the migration the lead must apply before running npm run deploy --workspace @hourmere/site in the Cobaltmere Cloudflare account.
The privacy policy's effective and last-updated dates and the terms' effective date share
LEGAL_PUBLICATION_DATE in apps/site/src/data/legal.ts, currently 2026-10-22.
Keep that date on later deployments, including documentation and homepage changes; change it
only when publishing revised legal text. A placeholder or invalid date stops the site build.
Environment variables cannot override the source date. CI retains a fixture-date substitution
for a placeholder source date; with the current committed date, that substitution makes no
change. Its build steps restore the source with an exit trap and delete the generated site
output. Never deploy fixture output or commit a fixture date.
The Starlight documentation includes a self-hosting guide, configuration reference, and REST / MCP quickstarts.
Requires Git, OpenSSL, Docker Engine and Docker Compose 2.24.4 or newer. Point your hostname at the host with an A record only and allow TCP 80/443 and UDP 443 (plus SSH) before using automatic HTTPS:
git clone https://github.com/hourmere/hourmere.git
cd hourmere
./scripts/setup.sh --source
docker compose up -d --build --wait
curl --retry 20 --retry-delay 3 --retry-all-errors --fail https://hourmere.example.com/api/health
Accept proxy, enter your HTTPS origin (replace hourmere.example.com with your hostname),
and configure SMTP and its sender. Setup generates separate secrets directly into a mode
600 .env, refuses to overwrite an existing file, and selects the optional Caddy override.
Caddy obtains/renews TLS certificates; only TCP 80/443 and UDP 443 are published, and the app trusts only
the proxy's dedicated address for forwarded client IPs. If its network overlaps an existing
network or another instance, change PROXY_SUBNET, PROXY_IP and PROXY_DYNAMIC_RANGE together
in .env before starting. No prebuilt public image is
published yet; Compose builds the checked-out source. Application and job migrations run
automatically (AUTO_MIGRATE=true). Open /sign-in and claim the first account immediately;
SIGNUP=first-user restricts later registration as described below.
Select plain for an existing proxy or an explicitly acknowledged private loopback trial.
It keeps the app on 127.0.0.1:3000 and PostgreSQL unpublished. For your own proxy, set its
exact address as the app sees it in TRUSTED_PROXIES and preserve Host/forwarded headers;
see the self-hosting guide for the complete
TLS, backup/restore, upgrade and host-hardening procedures. The default app also runs
background jobs; to split them after setup, run:
(
unset COMPOSE_FILE &&
./scripts/enable-worker.sh &&
compose_services=$(docker compose config --services) &&
{ printf '%s\n' "$compose_services" | grep -qx worker ||
{ echo "Worker service is missing; check COMPOSE_FILE before continuing."; exit 1; }; } &&
{ printf '%s\n' "$compose_services" | grep -qx proxy ||
{ echo "Proxy service is missing; check COMPOSE_FILE before continuing."; exit 1; }; } &&
docker compose up -d --build --wait
)
For plain mode, remove both lines of the proxy verification (the grep -qx proxy line
and its following failure-message line) before pasting the block.
The helper safely updates COMPOSE_FILE
in the private .env, preserving the proxy selection. The tracked overlay automatically
selects web for the app and worker for the separate worker, using the same image and
secrets.
The block unsets a shell-exported COMPOSE_FILE only inside its subshell; unset it
separately before standalone Compose commands so they read .env too.
The .env backup preserves this choice, and upgrades need no tracked Compose edits.
Give containers at least 35 seconds to stop.
Before upgrading, take a full backup and record the current branch name or tag alongside
the backup's saved commit. A checkout-only rollback with
git checkout <previous branch or tag> is safe if a guard, docker compose pull or
docker compose stop fails before the new-version docker compose up command runs.
Once that up is attempted, follow Back up and restore,
even if it fails: a new app or worker may already have applied migrations; old code against
the new schema is unsafe. In the supplied Compose files, AUTO_MIGRATE=true runs application
and pg-boss migrations at process startup for every role (all, web and worker),
not during pull or stop; there is no separate migrate service. A separately started
new-version worker, npm run db:migrate or a custom migration job can migrate earlier;
if any has started, use backup/restore even before this Compose up.
For a failed main-tracking pull, the backup's saved commit identifies
the exact pre-upgrade revision: git pull advances main, so its branch name alone does
not undo the update. If rollback leaves a pre-1.0 main-tracking checkout detached, run
git switch main before the next upgrade so git pull --ff-only works.
Git does not undo enable-worker.sh's private .env edit. Before checking out the prior
revision, restore only the pre-upgrade COMPOSE_FILE assignment from the saved private
.env (remove the assignment if it was absent), preserving the proxy/custom overlays
and other current settings. See the guarded upgrade procedures.
For a pre-start failure, if the app or worker was already stopped (including a partial
stop failure), bring the old version back with the block below after restoring that
selection. Replace PREVIOUS_BRANCH_OR_TAG with the recorded name. If no service was
stopped, only the checkout is needed. The block uses the existing old application image;
do not add --build or pull an application image during this recovery.
(
git checkout "PREVIOUS_BRANCH_OR_TAG" &&
unset COMPOSE_FILE &&
docker compose up -d --wait
)
The application image retains small public attribution and booking-confirmation links to the
hosted service. HOSTED_SIGNUP_URL sets their destination (default https://hourmere.com);
there is no setting to hide them.
Set PRIVACY_POLICY_URL and TERMS_URL to your operator's absolute HTTP(S) legal-page
URLs to display them in the app. Both default to empty, which hides the corresponding
link; self-hosted installations do not inherit Hourmere Cloud's policies. The hosted
instance must explicitly set https://hourmere.com/privacy and https://hourmere.com/terms.
Team owners and admins can configure collective meetings with two to eight required hosts, or round-robin pools with one to eight hosts and explicit activation.
Website widgets support inline and popup booking; see embedding for the explicit parent-origin allow list, integration snippets and privacy contract.
Responses carry security headers: X-Content-Type-Options: nosniff, X-Frame-Options: DENY (except explicitly approved dedicated embed pages),
a Referrer-Policy, and on pages a Content-Security-Policy that lets only Hourmere's own
scripts, styles and requests run. When PUBLIC_URL is https, browsers are also told to use
only https for that host, for a year (HSTS_MAX_AGE). Clients that send too slowly are
disconnected: 20 s to send a request's headers and 60 s for the whole request
(HTTP_HEADERS_TIMEOUT, HTTP_REQUEST_TIMEOUT). Idle connections stay open for 125 s
(HTTP_KEEP_ALIVE_TIMEOUT): keep it above your reverse proxy's idle timeout for connections to
the app, or the proxy may reuse a connection the app has just closed and answer 502. The
default is above the usual ones: nginx's upstream keepalive and AWS ALB 60 s, Traefik 90 s,
Caddy 120 s.
People sign in with a link sent by email (no passwords), or with Google or Microsoft once their
client ids are set in .env. Accounts are only created for email addresses the provider
confirms: for Microsoft, add the optional ID token claims verified_primary_email and
verified_secondary_email in the Entra app registration (Token configuration), or Microsoft
users get EMAIL_NOT_VERIFIED and have to use an email link. Until SMTP_URL and MAIL_FROM are set, the server prints each
sign-in link to its log (docker compose logs app), so you can sign in right after the first
start. Anyone who can read that log can sign in as anyone: configure SMTP before inviting
anyone else, and always on a hosted or internet-facing instance. The setup script requires
SMTP for public deployment; empty SMTP is accepted only for an explicitly acknowledged
private loopback trial.
SIGNUP decides who may create an account; existing users can always sign in. With
first-user (the default), the first person to sign in gets an account, and after that only
addresses in SIGNUP_ALLOWED_DOMAINS (comma-separated email domains, e.g.
example.com,example.org; exact match, so list subdomains separately). So sign in right
after the first start: the first account should be yours. closed allows those domains and
the invitation exception below. With an empty domain list, only invitations from the first
user admit new accounts, so bootstrap an empty instance in first-user mode or through an
administrator before using closed. open lets anyone who can reach the instance sign up.
A pending, unexpired team invitation allows its exact email address in either restricted
mode only if the inviter's current email domain is allowed or the inviter is the instance's
first user (the earliest remaining auth user by creation time, with user id breaking ties).
The original inviter must also still belong to the invitation's team; demotion while
remaining a member preserves this eligibility. A void invitation grants no account or
sign-in email to an otherwise ineligible address.
Invitation-based registration is non-transitive: an invitee outside the allowed domains
cannot admit another address by creating a team and inviting it. Team creation and invitation
acceptance grant no registration authority. These rules apply to existing invitations too,
using the current domain allowlist. Sign-up still requires proving that email address;
joining the team requires accepting the invitation separately. Expired, canceled or already
accepted invitations do not allow new accounts. A new
address that may not sign up gets no sign-in email, and the request is answered the same way
as for any other address. Every request queues an encrypted, short-lived email job before eligibility is checked by
a worker; the request does not wait for SMTP or check whether the address has an account.
Workers make at most four delivery attempts and discard expired links. Request a new link
if delivery fails. In web-only mode, a separate worker
is required for sign-in emails as well as booking notifications. Failures appear in the log
without the recipient or link.
Team invitation links open /app/join?invitationId=<uuid>. Sign in with the exact email
address that received the invitation; email-link and enabled social sign-in return to that
page. Signing in does not join the team: review the team and signed-in account, then choose Join team.
After joining, the event types page confirms the team name and links to your default
availability. Dismiss this one-time notice, navigate away or reload to clear it.
If you used a different account, Change account signs you out while keeping the invitation
for your next sign-in. Invitation details remain restricted to the verified recipient.
An existing member can open the team without accepting again.
If the original inviter leaves or is removed from the team, their pending invitations are
no longer valid. Ask a current team owner/admin for a new invitation; those old links cannot
be accepted or resent, and recipient invitation discovery omits them. An inviter's demotion
while remaining a member does not invalidate links.
The signed-in header's space menu switches between Personal and your member organizations and shows your role in the current space. Switching asks before discarding unsaved changes and starts the page over in the selected space. A failed switch keeps the current page; returning to personal after team access or active context changes shows a notice. Switching the web app does not change an API key's organization or an existing MCP OAuth grant.
In a team space, Settings lists joined members by name and role with pagination. Owners and admins also see a separate paged section of pending invitations and their mail submission status. Mail server acceptance does not confirm inbox receipt; pending invitations never count as members or hosts. Owners/admins can resend or cancel each pending invitation, and select expired invitations to atomically cancel the old invitation and send a new invitation with a new link under the acting admin, without a duplicate pending row. Cancellation never removes a member who has already joined. Invitations from someone who no longer belongs to the team show No longer valid; cancel the old invitation and use the invite form to send a fresh invitation in your own name. The expired-row replacement action cannot reuse these invitations. Owners can promote members to admins or return admins to member access after reviewing the permission change. Only owners manage admins; owner roles cannot be changed, and ownership transfer is not available yet. A team must always retain at least one owner.
Member rows show shared configuration and cached calendar health checks, without promising available times. Owners and admins see only coarse states for other members, including whether they have selected a conflict calendar or connected a conference account; ordinary members see their own readiness. Providers, calendar and connection IDs, account counts, private calendar names, account emails, events and busy times are excluded. Your own row links to schedule, calendar and conference settings and refreshes when you return. Your personal default schedule is reused across every team you belong to; an external calendar is optional for basic bookings.
The team event host editor shows the same coarse checks for selected hosts. Your own setup links open your settings; other hosts' setup links open the members page with guidance to ask the member to make private changes. Owners and admins can open a member's hosted team event types from that member's row, including dormant round-robin pool candidates.
The database enforces the sign-up rules too, using settings the app passes in the options
startup parameter when it connects (search_path and hourmere.*), and it needs read
committed isolation (Postgres's default) or serializable. Connect the app to Postgres
directly, as the compose file does; connection poolers such as PgBouncer are not supported,
since they do not pass custom startup settings like hourmere.* on to Postgres. The app reads
the settings back at startup and refuses to start if they are missing.
That check looks at one connection, once, at startup: a pooler slipped in later at the same
address would turn the database's check off without notice (the app's own check still
applies). It also means the app needs the database at startup even with AUTO_MIGRATE=false;
if the database is down, the app exits and compose restarts it.
Sign-in links open a page with a "Sign in to Hourmere" button, and only pressing it signs in: email security scanners that open every link (such as Microsoft Safe Links) would otherwise use up the one-time link before the person clicks it.
Agents use API keys (hm_…) sent as Authorization: Bearer <key>. Create them on the web
app's API keys page (/app/api-keys), or with Better Auth's endpoints while signed in (POST /api/auth/api-key/create, GET /api/auth/api-key/list, POST /api/auth/api-key/delete). The API is described at
/api/v1/openapi.json; its conventions are in API conventions.
The same keys connect MCP clients such as Claude Code to the remote MCP server at /mcp; see
MCP.
Choose Bookings (the web default) for an AI booking agent, Read for
analytics/reporting, or Full account access when you need configuration writes. Existing
keys retain full access. A key's scope cannot change: create a replacement, verify it, then
revoke the old key. See API key scopes for the generated
operation table and the required stop-before-upgrade procedure.
For terminal scripts, the dependency-free CLI supports configuration discovery, slots, booking, host history, rescheduling and cancellation with the same API key.
Configure event-type reminders and follow-ups through REST, MCP
set_event_type_notifications or CLI set-event-notifications with full scope.
Follow-up rules may carry bounded plain-text host notes. These are sent from the instance's
mail domain to unverified invitee addresses; operators can disable writes and sending of
notes with FOLLOW_UP_HOST_NOTES_ENABLED=false (self-hosting default: true). Configure
web and worker identically. Hourmere Cloud must explicitly set this to false until
reviewed host-text abuse controls are implemented. See notification settings and host-note policy.
Organization owners can view first-party booking activity at
/app/analytics: daily creations, reschedules and actual cancellations, without visitor
tracking or attendee details.
Optional independent Zoom account connections provide a separate minimum-scope identity foundation. Explicit opt-in automatic Zoom supports personal and single-host team bookings with durable reconciliation, same-meeting rescheduling and cancellation, ready-only invitations and an optional ordinary Google calendar copy. Zoom is not a sign-in provider. See the Zoom account guide before configuring a real client.
Optional automatic Google Meet creates one conference per explicitly opted-in individual, collective or round-robin booking, with durable pending/failure state and final invitations held until its link is ready.
See the consolidated limitations and linked operating guides before deploying:
Bug reports and ideas are welcome in GitHub Issues, as are pull requests. CONTRIBUTING.md covers setup, checks, conventions and the DCO sign-off; everyone follows the code of conduct.
Report vulnerabilities privately through the repository's Security tab or to [email protected], not in a public issue. See SECURITY.md.