ai-startup-skills
AI 제품을 만드는 스타트업을 위한 7단계 에이전트 스킬: 검증 → 비즈플랜 → 정책 → 로드쇼 → 고객 확보 → 가격 책정 → 등록
저장소 정보는 아직 수집하지 않았습니다.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Web app for Sabre coy (8SIR) company processes. v1 replaces the parade-state Telegram bot (legacy/paradestate.py) and its Google Sheets.
New session? Read docs/STATUS.md first. It covers what's live, the domain rules, the decisions log, known limitations and where v2 starts.
Next.js 16 (App Router) · TypeScript · Tailwind + shadcn/ui (Base UI) · Postgres + Drizzle · Vitest.
Sign-in: one shared password (APP_PASSWORD). Each person also types their rank and name, which is recorded against every change and in History. Everyone signed in has full access. Ten wrong passwords from one IP blocks it for 15 minutes, and changing APP_PASSWORD signs everyone out. The role and subunitId columns and the assertCanEdit* checks are still there for when individual logins return.
| Path | What |
|---|---|
lib/parade/ | The engine. A pure TypeScript port of the bot's counting, formatting, validation and message splitting. No DB or I/O. |
lib/db/schema.ts | Tables: coy › subunit › camp (with post = physical camp) › person, absence (open = absent, closed = history, duty_id for automatic RF/Extra absences), duty (Extra/RF/SOL), parade_state (history), audit_log, user, app_session, login_failure. |
lib/actions/ | Server actions. Every one checks the signed-in user first. |
lib/session.ts, lib/authz.ts | Shared-password sessions and access checks. |
lib/data/ | Snapshot loading (snapshot.ts), per-request memo (current.ts), and the Sheets import (parse-export.ts, write-coy.ts). |
app/(app)/ | Screens: Overview, Platoon /s/[id], Camp /c/[id], Duties, Parade, History, Admin. |
scripts/ | export_from_sheets.py and import-sabre.ts (the one-time migration), plus seed-example.ts. |
tests/ | Golden tests against the bot's real output, validation, import and DB round-trip tests. |
docs/ | STATUS.md (start here), v1-plan.md (historical). |
The parade-state text matches the bot character for character. tests/fixtures/example-300926.txt is a real bot output, and the tests render it from the engine, from the database, and from a simulated Sheets import.
The app is stricter than the bot in a few places: it ignores off-shift camps, follows the shift rules for SOL, and never lets RF/Extra count twice. On consistent data it prints the same text. See docs/STATUS.md §2.
npm install
createdb sabre_dev && createdb sabre_test
cp .env.example .env.local # fill in DATABASE_URL, AUTH_SECRET (openssl rand -base64 32), APP_PASSWORD
npm run db:migrate && npm run db:migrate:test
npm run seed # example Sabre data (placeholder names), or `npm run import`, see below
npm run dev
npm test # all tests (DB tests use sabre_test)
npm run typecheck
npm run lint
The import updates people in place: each Sheet row is matched to the person already in the app (same camp and name, or the same name in another camp when only one person has it), then edited, moved or added. Anyone the Sheet no longer has is marked inactive, never deleted, and comes back with their history if the Sheet has them again. Users, platoon assignments, history and whereabouts data are kept. By default absences, duties, on-shift and the Telegram chat are left as the app has them.
credentials.json in the repo root. It is gitignored and should be treated like a password.python3 -m venv .venv && ./.venv/bin/pip install --upgrade gspread pandas python-telegram-bot python-dotenv google-auth./.venv/bin/python scripts/export_from_sheets.py "PL5-8 COY" uses the bot's own code to read and validate the Sheets. Sabre's key in the bot's config sheet is PL5-8 COY.
sabre-export.json. This file contains personal data: don't commit or share it, and delete it after importing.npm run import -- --dry-run prints what the import would do (edited, moved, back, added, removed, likely renames) and writes nothing. Check it first.
28/09/26 instead of 280926.npm run import updates people and structure. If anyone would be marked inactive it stops and asks you to run it again with --yes.
npm run import -- --with-absences --yes also replaces every absence, duty, on-shift and the Telegram chat with the Sheet's, then renders the parade state from the database at the bot's CAA and compares it with the bot's output. ✅ PARITY means the app prints exactly what the bot printed. It overwrites what GCs typed in the app.Which database: the import writes to DATABASE_URL from .env.local, which is your local database (its first line names the host). The whereabouts preview and production are separate databases.
Into the whereabouts preview: .env.local holds its unpooled URL as WB_PREVIEW_DATABASE_URL:
DATABASE_URL="$(grep '^WB_PREVIEW_DATABASE_URL=' .env.local | cut -d= -f2- | tr -d '"')" npm run import -- --dry-run
Into production: .env.production.local (gitignored) holds the production DATABASE_URL_UNPOOLED.
DATABASE_URL="$(grep '^DATABASE_URL_UNPOOLED=' .env.production.local | cut -d= -f2- | tr -d '"')" npm run import -- --dry-run
rm sabre-export.json
Then the same without --dry-run (plus --yes if it asks). After cutover the app is the source of truth for attendance: never use --with-absences then. A plain import (people only) stays safe.
An SOL typed under the other team (e.g. SFT B) is automatically tied to the person in SFT A when the name matches.
Environment variables to set in Vercel:
| Variable | Value |
|---|---|
DATABASE_URL / DATABASE_URL_UNPOOLED | Neon Postgres, Singapore region. Added automatically by the Marketplace integration. The app uses the pooled URL; migrations and imports use the unpooled one. |
AUTH_SECRET | openssl rand -base64 32 |
APP_PASSWORD | The shared sign-in password. Make it long, e.g. four random words. |
TELEGRAM_BOT_TOKEN | The old bot's token (the TOKEN= line in .env on the VM) |
Functions are pinned to Singapore (sin1) in vercel.json, next to the database. Pushing to main deploys to production.
Migrations: run them against production before pushing code that needs them, and keep new columns additive or nullable so the running deploy keeps working:
DATABASE_URL="$(grep '^DATABASE_URL_UNPOOLED=' .env.production.local | cut -d= -f2- | tr -d '"')" npx drizzle-kit migrate
Telegram: the chat ID and topic ID are set in Admin › Coy settings, with a Send test message button. The bot must be a member of the group.
.github/workflows/backup.yml runs every night at 0300 SGT (and on demand from the Actions tab). It runs scripts/backup.sh, which:
The file is kept as a workflow artifact for 30 days and, if the backup bot is set up, sent to its Telegram chat. The dump holds medical data, so it is never stored unencrypted. A failed run emails the repo owner. Neon's own restore window (6 hours on the free plan) covers mistakes from the last few hours.
Setup (once, in GitHub › Settings › Secrets and variables › Actions):
age-keygen -o ~/sabre-backup-key.txt (brew install age) and keeps that file safe, e.g. in a password manager. It prints a public key, age1….BACKUP_AGE_RECIPIENTS: the public keys, one per line.BACKUP_DATABASE_URL: production's unpooled Neon URL.BACKUP_TELEGRAM_BOT_TOKEN and variable BACKUP_TELEGRAM_CHAT_ID (your Telegram id).main.Restoring (into an empty database: a new Neon project or branch, or a local scratch one). Needs pg_restore 18 (brew install libpq):
PATH="/opt/homebrew/opt/libpq/bin:$PATH" scripts/restore-backup.sh sabre-2026-10-25-0300.dump.age ~/sabre-backup-key.txt "<empty database URL>"
It refuses a database that already has tables, and prints every table's row count at the end. Then point DATABASE_URL at it. Monthly drill: restore the latest file into a scratch Neon branch and check the counts look right.
✅ PARITY.pkill -f paradestate.py.See docs/STATUS.md §5: report sick, MC tracking, leave and transport claims, plus individual logins.