Skip to main content

launch-light

Lightweight productivity bootstrap for greenfield TypeScript projects — pnpm workspace with apps/api/ (NestJS) by default, BFF-ready so adding apps/web/ later requires zero restructuring. OpenRouter + LangSmith env slots, a colored start-services.sh launcher that hard-fails when a service doesn't come up, README, and .env.example. NO Docker, NO Postgres, NO infra. Generates a runnable scaffold in seconds without asking domain questions. Use when the user says "/launch-light", "init", "init setup", "bootstrap minimal", "spin up Nest", "give me a scratch Nest app". Hands off to /launch-scratch-project the moment Docker/Postgres/Redis/queues/multi-service enter the picture.

Jump to install

Source facts

Repository
DevOtts/all-skills-you-will-ever-need
Last source activity
May 26, 2026 at 01:49
Detected SKILL.md language
English
Stars
4
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
launch-light
description
Lightweight productivity bootstrap for greenfield TypeScript projects — pnpm workspace with apps/api/ (NestJS) by default, BFF-ready so adding apps/web/ later requires zero restructuring. OpenRouter + LangSmith env slots, a colored start-services.sh launcher that hard-fails when a service doesn't come up, README, and .env.example. NO Docker, NO Postgres, NO infra. Generates a runnable scaffold in seconds without asking domain questions. Use when the user says "/launch-light", "init", "init setup", "bootstrap minimal", "spin up Nest", "give me a scratch Nest app". Hands off to /launch-scratch-project the moment Docker/Postgres/Redis/queues/multi-service enter the picture.
author
DevOtts
author_url
https://github.com/DevOtts
# launch-light Fast, opinionated bootstrap for a greenfield TypeScript service. **Always BFF-ready**: scaffolds a pnpm workspace with `apps/api/` (NestJS) on day 1, so when someone later asks for a frontend it's just `apps/web/` dropped in next to it — no restructuring, no shared-tsconfig surprises, no "the API tsconfig swept the React code" failure modes. **No Docker. No databases. No queues.** The moment the user needs any of those, hand off to `/launch-scratch-project`. ## Soft reminder (not a gate) If this scaffold is for a graded exercise (interview, take-home, coaching session), spend a minute framing the problem out loud first — the scaffold is intentionally trivial so the graded thinking happens after, not during. But this skill will *not* refuse to run if you skip the framing — that's the user's call. ## When to use this skill vs `/launch-scratch-project` | Use `launch-light` | Use `/launch-scratch-project` | |---|---| | NestJS API (and optionally a Next.js/Vite app later) | Need Postgres/Redis/MongoDB/RabbitMQ/MinIO/etc. | | In-memory state or JSON-file DB | Need a docker-compose stack | | Single-shot scaffold, no questionnaire | Want the multi-service questionnaire + plan-approval flow | | `start-services.sh` for `pnpm dev` | `init.sh` for `docker compose up` + per-service docs | | < 5 minutes of bootstrap | Multi-service spike or interview build | If the user mentions any of: **Postgres, Redis, MongoDB, RabbitMQ, Kafka, MinIO, MailHog, ClickHouse, docker, queue, broker, persistence, multi-service** → STOP and say: > "That needs `/launch-scratch-project` — it has the questionnaire for picking services and generates the docker-compose stack. `/launch-light` is no-infra by design." Don't extend `launch-light` with Docker — that's the explicit boundary. ## Decoding the user's prompt There's only one mode: pnpm workspace with `apps/api/`. Parse for these optional flags: | User says | Effect | |---|---| | `/launch-light`, `init`, `init setup`, `bootstrap`, just NestJS | default workspace + apps/api | | `with frontend`, `with web`, `BFF`, `+ web`, `+ Next` | ALSO scaffold `apps/web/` (Next.js 15) on the same pass | | `--skip-llm` or `no openrouter` | drop OpenRouter env + helper | | `--skip-langsmith` or `no tracing` | drop LangSmith env + package | Don't ask a questionnaire. Generate, hand back. If the user later wants a frontend, `/iteration-impl` will drop `apps/web/` in alongside `apps/api/` — the workspace shape never has to change. ## What it generates ``` . ├── apps/ │ └── api/ # NestJS — :3001 — the single-app default │ ├── src/ │ │ ├── main.ts # bootstrap, listens on :3001 │ │ ├── app.module.ts │ │ ├── app.controller.ts # GET /health → { ok: true } │ │ ├── app.controller.spec.ts # seed failing test (intentional RED) │ │ └── llm/ │ │ └── openrouter.ts # configured OpenAI-SDK client → OpenRouter │ ├── package.json # "name": "api", nest start, vitest │ ├── tsconfig.json # scoped: include src/**/*.ts, exclude dist/node_modules │ ├── vitest.config.ts │ └── nest-cli.json ├── .env # symlinked into each app by start-services.sh ├── .env.example # OPENROUTER_*, LANGSMITH_*, PORT ├── .gitignore ├── package.json # workspace root (pnpm -r dev, no app deps here) ├── pnpm-workspace.yaml # packages: ["apps/*"] ├── start-services.sh # chmod +x — runs whatever apps it finds, hard-fails on unhealthy └── README.md ``` If the user invoked with `+ web` / BFF, ALSO scaffold: ``` └── apps/ └── web/ # Next.js 15 App Router — :3000 ├── app/page.tsx # calls API via /api proxy ├── app/api/health/route.ts ├── next.config.ts # rewrites /api/* → http://localhost:3001/* ├── package.json └── tsconfig.json # scoped to its own dir, "jsx": "preserve" ``` The workspace shape is the load-bearing decision. Adding `apps/web/` later (manually or via `/iteration-impl`) requires no restructuring because each app already has its own `package.json` and `tsconfig.json` — they were never coupled. In all cases: - **Vitest** is the test runner (not Jest — faster, consistent across web + api) - **One seed failing test** so TDD has a RED to start from - **`start-services.sh`** is the canonical launcher; `pnpm -r --parallel dev` works too - **`.runtime/`** holds process logs (gitignored) ## Phase 1 — Confirm and dispatch Print a one-screen confirmation (no questionnaire): ``` ## launch-light bootstrap Layout: pnpm workspace — apps/api/ [+ apps/web/ if requested] LLM: OpenRouter (env slot + apps/api/src/llm/openrouter.ts helper) [or "skipped"] Tracing: LangSmith (env slot + langsmith package) [or "skipped"] Runner: start-services.sh (colored, port preflight, watchdog, hard-fails on API/Web not ready) Tests: Vitest with one seed failing test Files I'll write: - package.json (workspace root), pnpm-workspace.yaml - apps/api/{package.json, tsconfig.json, vitest.config.ts, nest-cli.json} - apps/api/src/{main.ts, app.module.ts, app.controller.ts (+ spec)} - apps/api/src/llm/openrouter.ts (if LLM enabled) - apps/web/* (only if + web was requested) - .env.example, .gitignore - start-services.sh (chmod +x) - README.md Proceed? (y/n) ``` If `y` → write everything in one pass. If `n` → ask what to change. ## Phase 2 — Generate ### 2.1 Workspace root `pnpm-workspace.yaml`: ```yaml packages: - "apps/*" ``` `package.json` (workspace root): ```json { "name": "<project>", "private": true, "scripts": { "dev": "pnpm -r --parallel dev", "build": "pnpm -r build", "test": "pnpm -r test" }, "devDependencies": { "typescript": "^5.6.0" } } ``` ### 2.2 `apps/api/package.json` ```json { "name": "api", "version": "0.0.1", "private": true, "scripts": { "dev": "nest start --watch", "build": "nest build", "start": "node dist/main.js", "test": "vitest run", "test:watch": "vitest" }, "dependencies": { "@nestjs/common": "^10.4.0", "@nestjs/core": "^10.4.0", "@nestjs/platform-express": "^10.4.0", "dotenv": "^16.4.0", "openai": "^4.70.0", "langsmith": "^0.2.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, "devDependencies": { "@nestjs/cli": "^10.4.0", "@nestjs/testing": "^10.4.0", "@types/node": "^22.0.0", "typescript": "^5.6.0", "vitest": "^2.1.0", "vite-tsconfig-paths": "^5.0.0" } } ``` Drop `langsmith` if `--skip-langsmith`. Drop `openai` if `--skip-llm`. ### 2.3 `apps/api/tsconfig.json` — scoped from day 1 ```json { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": true, "noImplicitAny": true, "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "esModuleInterop": true, "resolveJsonModule": true }, "include": ["src/**/*.ts", "scripts/**/*.ts"], "exclude": ["node_modules", "dist"] } ``` **Why explicit `include`/`exclude`:** without them, `tsc --watch` (spawned by `nest start --watch`) sweeps the whole project tree. If a sibling folder ever appears (e.g. `apps/web/`, `evals/`, a scaffolded mockup) and the tsconfig is unscoped, tsc tries to compile its `.ts`/`.tsx` files, fails on JSX or React types, and **Nest never boots cleanly — the API silently dies and the only symptom is `Internal Server Error` from a frontend proxy.** Ship the tsconfig scoped from day 1; it costs nothing. ### 2.4 NestJS source files (`apps/api/src/`) **`main.ts`** ```typescript import 'dotenv/config'; // MUST be first — populates process.env before any module that reads env at load time (e.g. an OpenAI/LangSmith client constructed at module scope). nest start does NOT auto-load .env. import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); const port = Number(process.env.PORT ?? 3001); await app.listen(port); console.log(`API listening on http://localhost:${port}`); } bootstrap(); ``` **`app.module.ts`** ```typescript import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; @Module({ controllers: [AppController], }) export class AppModule {} ``` **`app.controller.ts`** ```typescript import { Controller, Get } from '@nestjs/common'; @Controller() export class AppController { @Get('health') health() { return { ok: true }; } } ``` **`app.controller.spec.ts`** (seed failing test — intentional RED) ```typescript import { describe, it, expect } from 'vitest'; import { AppController } from './app.controller'; describe('AppController', () => { it('health returns ok=true', () => { const c = new AppController(); expect(c.health()).toEqual({ ok: true }); }); it('TODO: replace this with the real first feature test', () => { expect.fail('Seed RED — delete or rewrite this test as your first real failing test.'); }); }); ``` The first test passes; the second is the deliberate RED that the TDD loop starts from. ### 2.5 OpenRouter helper (if LLM enabled) **`apps/api/src/llm/openrouter.ts`** ```typescript import OpenAI from 'openai'; import { wrapOpenAI } from 'langsmith/wrappers'; const apiKey = process.env.OPENROUTER_API_KEY; if (!apiKey) { throw new Error('OPENROUTER_API_KEY is missing — see .env.example'); } // `wrapOpenAI` emits LangSmith runs with token counts + cost estimates for every // `chat.completions.create` call. // // Two enhancements you almost always want on top of the bare wrapper: // // 1. NAME EVERY CALL. By default each run shows up in LangSmith as "ChatOpenAI", // which is useless when one request fires 3+ LLM calls. Pass a descriptive // `name` via `langsmithExtra` on each call — see `lsExtra` helper below. // // 2. WRAP THE CALLER WITH `traceable` TO GET A WATERFALL. Without a parent span // every `chat.completions.create` is a top-level run — flat list, no chain // of thought. Wrapping the function that makes the calls creates a parent // span; nested wrapOpenAI calls auto-attach as children via AsyncLocalStorage. // See "LangSmith tracing waterfall" below for the pattern. // // COST CAVEAT: LangSmith computes cost from its built-in price table for the // un-prefixed model id (e.g. `claude-sonnet-4-5`, not the OpenRouter slug // `anthropic/claude-sonnet-4.5`). OpenRouter adds margin on top, so the "cost" // number is directional, not your actual OpenRouter bill. Token counts are // correct. export const openrouter = wrapOpenAI( new OpenAI({ apiKey, baseURL: 'https://openrouter.ai/api/v1', defaultHeaders: { 'HTTP-Referer': process.env.OPENROUTER_SITE_URL ?? 'http://localhost:3001', 'X-Title': process.env.OPENROUTER_APP_NAME ?? '<project>', }, }), ); export const DEFAULT_MODEL = process.env.OPENROUTER_MODEL ?? 'anthropic/claude-sonnet-4.5'; // LangSmith's pricing table keys on un-prefixed canonical model ids // (e.g. `claude-sonnet-4-5`, not the OpenRouter slug `anthropic/claude-sonnet-4.5`). export function toLangSmithModelName(openrouterModelId: string): string {
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub