Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Book the crew. Run the job. Pay everyone the same day. AI agent for on-call crews: Bryntum Scheduler planning, availability links, hours reconciliation, PayPal Payouts. PayPal AI Hackathon 2026.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Book the crew. Run the job. Pay everyone the same day.
Shiftly is a workspace for a small operator who runs on-call crews: event stand builds, stage rigging, catering, removals. An AI agent turns a free-text brief into shifts, plans who works when on a Bryntum Scheduler, collects availability from freelancers on their phones, re-plans when someone declines, reconciles planned against actual hours, and computes pay. After the operator approves the total, it pays everyone through PayPal Payouts (Sandbox).
Built for the PayPal AI Hackathon 2026. MIT licensed.
| Step | Screen | Who acts |
|---|---|---|
| 1. Describe the job in plain words | /jobs/new | Operator writes, agent extracts dates, shifts, roles, client, budget |
| 2. Plan the crew | /jobs/[id] (Scheduler) | Agent proposes people; operator can drag shifts between people |
| 3. Send availability requests | Job page → Invitations | Operator sends; each person gets one signed link |
| 4. Accept or decline | /confirm/[token] (public, mobile) | Freelancer |
| 5. Rebalance a decline | Job page → Agent panel | Agent proposes a replacement; operator sends it |
| 6. Start the job, close shifts | /jobs/[id]/hours | Operator enters actual hours; agent flags differences over 15 min |
| 7. Pay the crew | Job page → Pay crew dialog | Operator confirms the total; PayPal Payouts sends it |
| 8. Track payouts | /payroll | PayPal reports item states; operator retries failures |
Job lifecycle: Planning → Crew confirmed → In progress → Done · to pay → Paid.
The agent uses OpenAI with structured JSON output (strict schemas) for three tasks:
src/lib/planning/constraints.ts, allocate.ts): role match, marked availability, no overlapping shifts, minimum rest between working days (11 h by default).If the AI service is down:
Rebalancing. When a freelancer declines, only that person's shifts are re-planned, after their answer is saved. The decliner is never offered the same shift again. The replacement is logged ("Luca declined Sat 14 Nov. Paolo takes over: …") and stays Proposed, not sent until the operator sends it.
Hours. Actual paid time is entered in 15-minute steps. Differences of more than 15 minutes (exactly 15 is fine) are flagged with a note. If the change pushes the day past the overtime threshold, the note says so. Approval needs every flag marked reviewed.
Pay. src/lib/pay.ts is pure and integer-only:
https://api-m.sandbox.paypal.com; no setting can point it at live money.SUCCESS item, and anyone whose money may still arrive: pending, on hold or unclaimed.ActivityLog row with an actor (operator, agent, paypal or crew), shown in the job's Agent panel.Payouts isn't covered by @paypal/paypal-server-sdk, so Shiftly calls the REST API with fetch (src/lib/paypal/client.ts). It uses OAuth client credentials with a cached token and a 20 s timeout. Errors fall into two kinds:
Pay crew (approvePayout)
payLines).planPayout decides what to do:
PayoutBatch with senderBatchId = job-{jobId}-{n} and one PayoutItem per person with senderItemId = job-{jobId}-worker-{workerId}-{n}. Then log payout.approved (operator).POST /v1/payments/payouts with header PayPal-Request-Id = senderBatchId. Store paypalBatchId, then fetch the batch to record per-item ids and states.Retry failed (retryFailed, from Payroll)
FAILED, at their approved amounts.retryOfId), so the failure stays visible in history.Unknown outcome
DRAFT and the UI says "We couldn't verify the payout response. Don't send again yet."PayPal-Request-Id and sender ids, so PayPal returns the original answer instead of paying twice.PayPal refused (validation, insufficient funds, credentials)
REJECTED with PayPal's message and "No money moved".Double clicks
senderBatchId makes the second insert fail, and that request reuses the first batch.PayPal webhook ──► POST /api/paypal/webhook ──► verify signature ──┐
├─► refreshBatch ─► GET /v1/payments/payouts/{id} ─► applyBatchStatus
cron / job page ─► POST /api/paypal/reconcile (x-cron-secret) ─────┘
POST /api/paypal/webhook:
POST /v1/notifications/verify-webhook-signature using PAYPAL_WEBHOOK_ID;PAYMENT.PAYOUTSBATCH.SUCCESS|DENIED|PROCESSING and PAYMENT.PAYOUTS-ITEM.SUCCEEDED|FAILED|BLOCKED|UNCLAIMED|RETURNED|REFUNDED|CANCELED;503 until PAYPAL_WEBHOOK_ID is set.POST /api/paypal/reconcile:
x-cron-secret: $SHIFTLY_CRON_SECRET;{ "jobId": 4 } or { "batchId": 2 } narrows it.applyBatchStatus (src/lib/payouts/service.ts):
SELECT … FOR UPDATE) and merges PayPal's snapshot through the pure mergeSnapshot;Done → Paid once every person with approved pay has a SUCCESS item.PayPal transaction_status | Shown as | Notes |
|---|---|---|
SUCCESS | Paid | The only state counted as paid |
NEW, PENDING, PROCESSING | Pending | Polled until final |
ONHOLD | On hold | PayPal review; not retried |
UNCLAIMED | Unclaimed | Recipient has no PayPal account yet. PayPal emails a sign-up link and returns the money after 30 days. Never re-sent |
FAILED | Failed | The only state Retry failed acts on |
RETURNED, REFUNDED, BLOCKED, CANCELED | Returned, Refunded, Blocked, Canceled | Final |
DRAFT (Shiftly) | Not sent | Saved, PayPal's answer not confirmed yet |
REJECTED (Shiftly) | Rejected | PayPal refused the request; no money moved |
A batch's label (Completed, Partially paid, Processing, Needs attention) is computed from its items, never from batch_status alone. Hovering a pill shows PayPal's raw status.
npm run payouts:smoke (scripts/payouts-smoke.ts) is the original Sandbox gate. It sends 3 × 1,00 € to SHIFTLY_PAYOUT_RECIPIENTS and polls until the batch settles.
@bryntum/scheduler@npm:@bryntum/scheduler-trial.next build past 8 GB of memory on Render.
scripts/vendor-bryntum.mjs runs before dev and build. It copies the prebuilt scheduler.module.js, scheduler.css, svalbard-light.css and the Font Awesome files into public/vendor/bryntum/ (gitignored).bryntum.ts loads them in the browser: the module through import(/* webpackIgnore: true */ url), the CSS as <link>s placed before the app's own styles. Only Bryntum's TypeScript types are imported at build time.scheduler-view.tsx creates a Scheduler once the module has loaded, then pushes data, zoom, height and the read-only state through Bryntum's runtime API (eventStore.data, viewPreset, setTimeSpan, …). The React wrapper isn't used, because it imports @bryntum/scheduler statically and would pull it back into the bundle. A skeleton shows while Bryntum loads; if it fails to load, the card says Couldn't load schedule. with a Retry button.timeRanges, plus a Now line.eventRenderer adds computed classes: role-installer|electrician|rigger|driver, status-proposed|sent|confirmed|declined, declined and conflict. The label shows the time window and role, plus a status icon.constrainDragToTimeSlot. Before the drop is final, the browser runs the same constraint checker as the server (checkAssignment) and rejects the move with the exact reason, e.g. "Giulia needs 11 hours of rest between working days (only 9h)". The server checks again before saving.Differences from docs/design/design-system.md §06:
Today button and no settings icon in the toolbar. Day chips (one per job day) jump to each day instead.Now marker uses Bryntum's built-in current-time line.The APIMatic Context Plugin for PayPal was installed during development (npx context-plugins install paypal, v0.4.1). It gives the coding agent PayPal SDK skills: setup, auth, endpoints, errors, models and testing for TypeScript, Python and .NET.
It was used to check what @paypal/paypal-server-sdk covers. Its TypeScript SDK map documents Orders but not Payouts, so following the brief, Payouts, OAuth and webhook verification are plain REST calls in src/lib/paypal/client.ts.
@prisma/adapter-pg on PostgreSQL (Render)public/vendor/bryntumOPENAI_MODELS: first = preferred, the rest are fallbacks)next/font); icons LucidetsxPrerequisites: Node 22+ and a PostgreSQL database (local, or the Render external URL).
cp .env.example .env.local # fill in the values (names below)
npm ci --ignore-scripts # see note on the Bryntum trial below
npx prisma generate
npm run db:deploy # apply migrations
npm run db:seed # demo company, crew and two jobs
npm run dev # http://localhost:3000
The Bryntum trial package ships an obfuscated postinstall script that the app doesn't need. Installing with --ignore-scripts skips it, which is also what the Render build does. Prisma's client is generated by npx prisma generate (and again by npm run build). npm run dev and npm run build first copy Bryntum's browser files into public/vendor/bryntum/.
Names only; see .env.example.
| Name | Used for |
|---|---|
DATABASE_URL | PostgreSQL connection string |
PAYPAL_CLIENT_ID, PAYPAL_SECRET | PayPal REST app credentials (Sandbox) |
PAYPAL_WEBHOOK_ID | Webhook signature verification; optional until the webhook is registered |
OPENAI_API_KEY | OpenAI API |
OPENAI_MODELS | Comma-separated model ids, first = preferred |
SHIFTLY_CRON_SECRET | x-cron-secret for /api/paypal/reconcile; also derives the invitation-link signing key |
SHIFTLY_PAYOUT_RECIPIENTS | Seed and smoke test only: 3 Sandbox Personal account emails |
| Script | What it does |
|---|---|
npm run dev / build / start | Next.js dev server, production build (runs prisma generate), production server |
npm test | Unit tests |
npm run lint / typecheck | ESLint, TypeScript |
npm run db:deploy | prisma migrate deploy |
npm run db:seed | Wipes all tables and seeds the demo |
npm run db:reset-demo | Applies pending migrations, then reseeds: back to the two seeded jobs; the next job created is J-0003 |
npm run payouts:smoke | Sandbox Payouts gate (3 × 1,00 €) |
The seed creates:
The three emails in SHIFTLY_PAYOUT_RECIPIENTS go to Elena Gallo, Sara Ricci and Paolo Ferrari. Everyone else has an @example.com address, so a demo payout shows real SUCCESS items next to UNCLAIMED ones. npm run db:reset-demo wipes everything else, including jobs created during a demo and their payout records.
npm test runs the unit tests in src/**/*.test.ts:
render.yaml defines one Node web service in Frankfurt:
npm ci --ignore-scripts && npx prisma migrate deploy && npm run buildnpm startnpm run db:reset-demo from a machine whose DATABASE_URL points at the Render database.On Render, set:
DATABASE_URL to the database's Internal URL. The app connects without TLS to private hostnames like dpg-…-a, and with verified TLS to external URLs.PAYPAL_CLIENT_ID, PAYPAL_SECRET, OPENAI_API_KEY, OPENAI_MODELS, SHIFTLY_CRON_SECRET.PAYPAL_WEBHOOK_ID once the webhook is registered at https://<service>.onrender.com/api/paypal/webhook.src/app/(operator)/ operator workspace: jobs, job detail, hours, crew, payroll, settings
src/app/confirm/[token]/ public, mobile invitation page
src/app/api/paypal/ webhook + reconcile routes
src/lib/planning/ constraints, allocation, planner (OpenAI + deterministic checks)
src/lib/payouts/ logic.ts (pure), service.ts (DB + PayPal), view.ts
src/lib/paypal/client.ts Sandbox REST client
src/lib/pay.ts, hours.ts pay and hours rules (pure)
prisma/ schema, migrations, seed
docs/ build brief and design handoff
These are deliberate scope cuts for the hackathon:
MIT, see LICENSE.
svalbard-light.css plus scheduler-theme.css, scoped to .shiftly-scheduler. It uses only Bryntum 7's documented CSS custom properties (--b-grid-*, --b-sch-event-*, --b-primary, …) and public classes, mapped to Shiftly's tokens.