Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Multi-tenant operations software for cleaning companies
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Multi-tenant operations software for cleaning companies. Built as a SaaS from day one — single tenant in production now, additional tenants onboardable without code changes.
The internal repo / package name is still cleanops from the original scaffold. The product name, all user-facing copy, and the visual identity are Sollos 3. Renaming the package would churn the lockfile + deploy history for zero user-visible benefit.README 전문 보기 ↗Multi-tenant operations software for cleaning companies. Built as a SaaS from day one — single tenant in production now, additional tenants onboardable without code changes.
The internal repo / package name is still
cleanopsfrom the original scaffold. The product name, all user-facing copy, and the visual identity are Sollos 3. Renaming the package would churn the lockfile + deploy history for zero user-visible benefit.
pnpm install
cp .env.local.example .env.local
Fill in .env.local with the values from your Supabase project (Settings → API).
pnpm dev
Visit http://localhost:3000.
cleanops/
├── src/
│ ├── app/
│ │ ├── (marketing)/ # public pages, login, signup
│ │ ├── (app)/ # ops console (admin/owner)
│ │ ├── (field)/ # field app (employees)
│ │ └── api/ # webhooks
│ ├── components/
│ │ └── ui/ # shadcn primitives
│ ├── lib/
│ │ ├── supabase/ # server / client / admin Supabase factories
│ │ ├── auth.ts # auth helpers
│ │ ├── audit.ts # audit log helper
│ │ └── validators/ # Zod schemas
│ └── hooks/
├── supabase/
│ ├── migrations/ # numbered SQL migrations
│ └── seed.sql
├── scripts/
│ └── seed.ts # idempotent dev seed
└── public/
organization_id and RLS policies enforce isolation at the database level.owner, admin, employee. Roles live on memberships, not profiles.audit_log.| Command | What it does |
|---|---|
pnpm dev | Run the Next.js dev server with Turbopack |
pnpm build | Production build |
pnpm start | Run the production build |
pnpm lint | Lint with ESLint |
pnpm seed | Reset and seed the dev database with realistic fake data (added in Phase 9) |
Every sensitive mutation in the ops console writes a row to audit_log via
src/lib/audit.ts. The owner / admin viewer lives at
Settings → Audit log (/app/settings/audit-log) and shows the most recent
200 events. The table is RLS-protected and append-only — there is no UPDATE
or DELETE policy at the database level.
Configured in next.config.ts. Highlights:
Content-Security-Policy with default-src 'self', scoped allowances for
the configured Supabase host (REST + Realtime), Sentry ingest, and Stripe.frame-ancestors 'none' + X-Frame-Options: DENY (anti-clickjacking).Strict-Transport-Security with a 2-year max-age, production only.Permissions-Policy denies camera / mic, allows geolocation only on
same-origin (the field clock-in flow needs it).Referrer-Policy: strict-origin-when-cross-origin.Scaffolded in env vars; install when ready:
pnpm add @sentry/nextjs
npx @sentry/wizard -i nextjs
The wizard populates NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG,
SENTRY_PROJECT, and SENTRY_AUTH_TOKEN. The CSP already allow-lists
https://*.ingest.sentry.io for the transport.
Scaffolded in env vars (RESEND_API_KEY, EMAIL_FROM). Install when ready:
pnpm add resend
Use it for: signup welcome, invoice sent, password reset, review-request links. Until the key is set, all email helpers no-op and log to the console so dev does not require a real provider.
Schema, webhook route, and billing portal stub are in place but the feature
flag STRIPE_ENABLED defaults to false:
supabase/migrations/20260408020000_subscriptions.sql — adds
the tenant-scoped subscriptions mirror table with read-only RLS for
members of the org.src/app/api/stripe/webhook/route.ts — returns 503 until
STRIPE_ENABLED=true. Skeleton dispatch for customer.subscription.*
events upserts into subscriptions via the service-role client.src/app/app/settings/billing/page.tsx shows the current plan and
a disabled "Manage in Stripe" button.To enable:
pnpm add stripe
# 1. Replace the placeholder in src/lib/stripe.ts with the real verifier:
# const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
# const event = stripe.webhooks.constructEvent(rawBody, signature, secret);
# 2. Set STRIPE_ENABLED=true + fill in the Stripe keys in your env.
# 3. Add the webhook URL in the Stripe dashboard:
# https://<your-domain>/api/stripe/webhook
Supabase runs an automated daily backup on every project. Verify in Supabase dashboard → Database → Backups that:
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY,
and SUPABASE_SERVICE_ROLE_KEY.Document the date + outcome in the team wiki so the drill is auditable.
Stub /privacy and /terms pages live so paying customers have something
to point at. Replace the wording with counsel-reviewed copy before the
first paid customer onboards.
Cleaning companies get last-minute callouts. The freelancer bench lets admins maintain a contact list of on-call cleaners (not Sollos users — no account needed) and broadcast a single shift to the whole list via SMS. The first freelancer to tap the unique claim link in their text gets the job; everyone else sees a "too slow" page.
Where it lives
/app/freelancers — contact CRUD + all broadcasted offers/app/bookings/[id]/offer — "Send to bench" form with live SMS preview/app/freelancers/offers/[id] — offer detail with per-dispatch status/claim/[token] — public no-login landing page the freelancer opens
from the SMS. Pre-claim it shows pay + time + rough area; post-claim it
reveals the full address and client phone.How the race is safe
The claim action runs with the service-role client and updates
job_offers with a status = 'open' guard — only one caller can flip it
to filled. The second caller's update returns zero rows and the page
falls into the "already claimed" state.
Twilio activation
Twilio is wired in but disabled by default — TWILIO_ENABLED=false in
.env.local.example. While disabled:
sendSms() logs the payload to the server console.delivery_status = 'skipped_disabled'.To go live:
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER
and set TWILIO_ENABLED=true in your environment (local + Vercel)./app/freelancers.Cost profile (rough): ~$55 first month + $11/mo carrier fees + about $0.014 per SMS segment. A 160-char offer to 10 freelancers costs ~$0.14 per broadcast.
Sollos is the dashboard; the cleaning company is the merchant of record. Money never touches a Sollos-owned account. When a client pays an invoice, funds move directly from the client into the cleaning company's connected processor account (Stripe, Square, or QuickBooks Payments).
/app/invoices/[id]) has a "Record payment" form for cash, check,
Zelle, Venmo, wire, etc. Payments are stored in invoice_payments
and a DB trigger recomputes the parent invoice's status and
paid_at based on the sum of payments — this is how partially_paid
works./i/<public_token> with the client — no login required. They see
the total, line items, payment instructions, and a grayed-out
"Pay with card — coming soon" CTA that lights up in Part 2.draft → sent (stamps
sent_at) and void invoices (sets voided_at and blocks new
payments)./app/settings/payment-methods (Zelle handle, mailing address, wire
details, etc). Every public invoice page pulls this as a fallback
unless an individual invoice overrides it./app/settings/integrations has three
provider cards (Stripe, Square, QuickBooks) that show "Coming soon"
until the platform OAuth credentials land in env, then "Not connected"
until the org clicks Connect. The underlying tables
(integration_connections, integration_events) and encryption
helpers (src/lib/crypto.ts, AES-256-GCM) are in place.INTEGRATION_ENCRYPTION_KEY. Generate
one with:
node -e "console.log('base64:' + require('crypto').randomBytes(32).toString('base64'))"
/api/integrations/<provider>/callback) and
populate the env vars in .env.local.example (Stripe Connect client
id, Square app id/secret, Intuit client id/secret, etc)./api/integrations/<provider>/callback route handlers
that complete the OAuth dance and insert into
integration_connections (tokens encrypted via encryptSecret())./api/integrations/<provider>/webhook route handlers with
idempotency via integration_events (provider, event_id) unique
index.integration_connections.access_token_ciphertext / refresh_token_ciphertext
store AES-256-GCM ciphertext (wire format: v1:iv:tag:ciphertext).
Even with service-role access, raw tokens never leave the app process.integration_events has a unique index on (provider, event_id) so
replayed webhooks are dropped safely.audit_log has a BEFORE UPDATE/DELETE trigger that raises, so even
the service-role key can't rewrite history.invoice_payments with provider IS NOT NULL can NOT be deleted
manually from the UI — only via a refund webhook from the processor.Push to main → Vercel auto-deploys. Environment variables are set in the Vercel dashboard, never committed.
This project follows the phased build plan in C:\Users\musil\.claude\plans\logical-soaring-engelbart.md.