diff --git a/.claude/skills/agent-browser/.openskills.json b/.claude/skills/agent-browser/.openskills.json index f7d1be0..b92f7b6 100644 --- a/.claude/skills/agent-browser/.openskills.json +++ b/.claude/skills/agent-browser/.openskills.json @@ -1,6 +1,6 @@ { - "source": "/tmp/skill-selector-curated-184743624", + "source": "/var/folders/vz/qhq7c61947bgqh97s4d3qnb80000gn/T/skill-selector-curated-2029108525", "sourceType": "local", - "localPath": "/tmp/skill-selector-curated-184743624/agent-browser", - "installedAt": "2026-04-21T04:29:26.875Z" + "localPath": "/var/folders/vz/qhq7c61947bgqh97s4d3qnb80000gn/T/skill-selector-curated-2029108525/agent-browser", + "installedAt": "2026-05-25T01:03:03.711Z" } \ No newline at end of file diff --git a/.claude/skills/agent-browser/SKILL.md b/.claude/skills/agent-browser/SKILL.md index 997b66e..cefd752 100644 --- a/.claude/skills/agent-browser/SKILL.md +++ b/.claude/skills/agent-browser/SKILL.md @@ -49,3 +49,7 @@ installed version. - Accessibility-tree snapshots with element refs for reliable interaction - Sessions, authentication vault, state persistence, video recording - Specialized skills for Electron apps, Slack, exploratory testing, cloud providers + +## Observability Dashboard + +The dashboard runs independently of browser sessions on port 4848 and can also be opened through a proxied or forwarded URL such as `https://dashboard.agent-browser.localhost`. Agents should stay on the dashboard origin: session tabs, status, and stream traffic are proxied internally, so session ports do not need to be exposed. diff --git a/.claude/skills/agentcore/.openskills.json b/.claude/skills/agentcore/.openskills.json deleted file mode 100644 index 25b76db..0000000 --- a/.claude/skills/agentcore/.openskills.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "source": "/tmp/skill-selector-curated-184743624", - "sourceType": "local", - "localPath": "/tmp/skill-selector-curated-184743624/agentcore", - "installedAt": "2026-04-21T04:29:26.883Z" -} \ No newline at end of file diff --git a/.claude/skills/agentcore/SKILL.md b/.claude/skills/agentcore/SKILL.md deleted file mode 100644 index 421f695..0000000 --- a/.claude/skills/agentcore/SKILL.md +++ /dev/null @@ -1,115 +0,0 @@ ---- -name: agentcore -description: Run agent-browser on AWS Bedrock AgentCore cloud browsers. Use when the user wants to use AgentCore, run browser automation on AWS, use a cloud browser with AWS credentials, or needs a managed browser session backed by AWS infrastructure. Triggers include "use agentcore", "run on AWS", "cloud browser with AWS", "bedrock browser", "agentcore session", or any task requiring AWS-hosted browser automation. -allowed-tools: Bash(agent-browser:*), Bash(npx agent-browser:*) ---- - -# AWS Bedrock AgentCore - -Run agent-browser on cloud browser sessions hosted by AWS Bedrock AgentCore. All standard agent-browser commands work identically; the only difference is where the browser runs. - -## Setup - -Credentials are resolved automatically: - -1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, optionally `AWS_SESSION_TOKEN`) -2. AWS CLI fallback (`aws configure export-credentials`), which supports SSO, IAM roles, and named profiles - -No additional setup is needed if the user already has working AWS credentials. - -## Core Workflow - -```bash -# Open a page on an AgentCore cloud browser -agent-browser -p agentcore open https://example.com - -# Everything else is the same as local Chrome -agent-browser snapshot -i -agent-browser click @e1 -agent-browser screenshot page.png -agent-browser close -``` - -## Environment Variables - -| Variable | Description | Default | -|----------|-------------|---------| -| `AGENTCORE_REGION` | AWS region | `us-east-1` | -| `AGENTCORE_BROWSER_ID` | Browser identifier | `aws.browser.v1` | -| `AGENTCORE_PROFILE_ID` | Persistent browser profile (cookies, localStorage) | (none) | -| `AGENTCORE_SESSION_TIMEOUT` | Session timeout in seconds | `3600` | -| `AWS_PROFILE` | AWS CLI profile for credential resolution | `default` | - -## Persistent Profiles - -Use `AGENTCORE_PROFILE_ID` to persist browser state across sessions. This is useful for maintaining login sessions: - -```bash -# First run: log in -AGENTCORE_PROFILE_ID=my-app agent-browser -p agentcore open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "user@example.com" -agent-browser fill @e2 "password" -agent-browser click @e3 -agent-browser close - -# Future runs: already authenticated -AGENTCORE_PROFILE_ID=my-app agent-browser -p agentcore open https://app.example.com/dashboard -``` - -## Live View - -When a session starts, AgentCore prints a Live View URL to stderr. Open it in a browser to watch the session in real time from the AWS Console: - -``` -Session: abc123-def456 -Live View: https://us-east-1.console.aws.amazon.com/bedrock-agentcore/browser/aws.browser.v1/session/abc123-def456# -``` - -## Region Selection - -```bash -# Default: us-east-1 -agent-browser -p agentcore open https://example.com - -# Explicit region -AGENTCORE_REGION=eu-west-1 agent-browser -p agentcore open https://example.com -``` - -## Credential Patterns - -```bash -# Explicit credentials (CI/CD, scripts) -export AWS_ACCESS_KEY_ID=AKIA... -export AWS_SECRET_ACCESS_KEY=... -agent-browser -p agentcore open https://example.com - -# SSO (interactive) -aws sso login --profile my-profile -AWS_PROFILE=my-profile agent-browser -p agentcore open https://example.com - -# IAM role / default credential chain -agent-browser -p agentcore open https://example.com -``` - -## Using with AGENT_BROWSER_PROVIDER - -Set the provider via environment variable to avoid passing `-p agentcore` on every command: - -```bash -export AGENT_BROWSER_PROVIDER=agentcore -export AGENTCORE_REGION=us-east-2 - -agent-browser open https://example.com -agent-browser snapshot -i -agent-browser click @e1 -agent-browser close -``` - -## Common Issues - -**"Failed to run aws CLI"** means AWS CLI is not installed or not in PATH. Either install it or set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` directly. - -**"AWS CLI failed: ... Run 'aws sso login'"** means SSO credentials have expired. Run `aws sso login` to refresh them. - -**Session timeout:** The default is 3600 seconds (1 hour). For longer tasks, increase with `AGENTCORE_SESSION_TIMEOUT=7200`. diff --git a/.claude/skills/auth-implementation-patterns/.openskills.json b/.claude/skills/auth-implementation-patterns/.openskills.json deleted file mode 100644 index f54cc82..0000000 --- a/.claude/skills/auth-implementation-patterns/.openskills.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "source": "/tmp/skill-selector-curated-3423638041", - "sourceType": "local", - "localPath": "/tmp/skill-selector-curated-3423638041/auth-implementation-patterns", - "installedAt": "2026-04-07T00:45:24.777Z" -} diff --git a/.claude/skills/auth-implementation-patterns/SKILL.md b/.claude/skills/auth-implementation-patterns/SKILL.md deleted file mode 100644 index 09d8e60..0000000 --- a/.claude/skills/auth-implementation-patterns/SKILL.md +++ /dev/null @@ -1,638 +0,0 @@ ---- -name: auth-implementation-patterns -description: Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues. ---- - -# Authentication & Authorization Implementation Patterns - -Build secure, scalable authentication and authorization systems using industry-standard patterns and modern best practices. - -## When to Use This Skill - -- Implementing user authentication systems -- Securing REST or GraphQL APIs -- Adding OAuth2/social login -- Implementing role-based access control (RBAC) -- Designing session management -- Migrating authentication systems -- Debugging auth issues -- Implementing SSO or multi-tenancy - -## Core Concepts - -### 1. Authentication vs Authorization - -**Authentication (AuthN)**: Who are you? - -- Verifying identity (username/password, OAuth, biometrics) -- Issuing credentials (sessions, tokens) -- Managing login/logout - -**Authorization (AuthZ)**: What can you do? - -- Permission checking -- Role-based access control (RBAC) -- Resource ownership validation -- Policy enforcement - -### 2. Authentication Strategies - -**Session-Based:** - -- Server stores session state -- Session ID in cookie -- Traditional, simple, stateful - -**Token-Based (JWT):** - -- Stateless, self-contained -- Scales horizontally -- Can store claims - -**OAuth2/OpenID Connect:** - -- Delegate authentication -- Social login (Google, GitHub) -- Enterprise SSO - -## JWT Authentication - -### Pattern 1: JWT Implementation - -```typescript -// JWT structure: header.payload.signature -import jwt from "jsonwebtoken"; -import { Request, Response, NextFunction } from "express"; - -interface JWTPayload { - userId: string; - email: string; - role: string; - iat: number; - exp: number; -} - -// Generate JWT -function generateTokens(userId: string, email: string, role: string) { - const accessToken = jwt.sign( - { userId, email, role }, - process.env.JWT_SECRET!, - { expiresIn: "15m" }, // Short-lived - ); - - const refreshToken = jwt.sign( - { userId }, - process.env.JWT_REFRESH_SECRET!, - { expiresIn: "7d" }, // Long-lived - ); - - return { accessToken, refreshToken }; -} - -// Verify JWT -function verifyToken(token: string): JWTPayload { - try { - return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload; - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - throw new Error("Token expired"); - } - if (error instanceof jwt.JsonWebTokenError) { - throw new Error("Invalid token"); - } - throw error; - } -} - -// Middleware -function authenticate(req: Request, res: Response, next: NextFunction) { - const authHeader = req.headers.authorization; - if (!authHeader?.startsWith("Bearer ")) { - return res.status(401).json({ error: "No token provided" }); - } - - const token = authHeader.substring(7); - try { - const payload = verifyToken(token); - req.user = payload; // Attach user to request - next(); - } catch (error) { - return res.status(401).json({ error: "Invalid token" }); - } -} - -// Usage -app.get("/api/profile", authenticate, (req, res) => { - res.json({ user: req.user }); -}); -``` - -### Pattern 2: Refresh Token Flow - -```typescript -interface StoredRefreshToken { - token: string; - userId: string; - expiresAt: Date; - createdAt: Date; -} - -class RefreshTokenService { - // Store refresh token in database - async storeRefreshToken(userId: string, refreshToken: string) { - const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); - await db.refreshTokens.create({ - token: await hash(refreshToken), // Hash before storing - userId, - expiresAt, - }); - } - - // Refresh access token - async refreshAccessToken(refreshToken: string) { - // Verify refresh token - let payload; - try { - payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as { - userId: string; - }; - } catch { - throw new Error("Invalid refresh token"); - } - - // Check if token exists in database - const storedToken = await db.refreshTokens.findOne({ - where: { - token: await hash(refreshToken), - userId: payload.userId, - expiresAt: { $gt: new Date() }, - }, - }); - - if (!storedToken) { - throw new Error("Refresh token not found or expired"); - } - - // Get user - const user = await db.users.findById(payload.userId); - if (!user) { - throw new Error("User not found"); - } - - // Generate new access token - const accessToken = jwt.sign( - { userId: user.id, email: user.email, role: user.role }, - process.env.JWT_SECRET!, - { expiresIn: "15m" }, - ); - - return { accessToken }; - } - - // Revoke refresh token (logout) - async revokeRefreshToken(refreshToken: string) { - await db.refreshTokens.deleteOne({ - token: await hash(refreshToken), - }); - } - - // Revoke all user tokens (logout all devices) - async revokeAllUserTokens(userId: string) { - await db.refreshTokens.deleteMany({ userId }); - } -} - -// API endpoints -app.post("/api/auth/refresh", async (req, res) => { - const { refreshToken } = req.body; - try { - const { accessToken } = - await refreshTokenService.refreshAccessToken(refreshToken); - res.json({ accessToken }); - } catch (error) { - res.status(401).json({ error: "Invalid refresh token" }); - } -}); - -app.post("/api/auth/logout", authenticate, async (req, res) => { - const { refreshToken } = req.body; - await refreshTokenService.revokeRefreshToken(refreshToken); - res.json({ message: "Logged out successfully" }); -}); -``` - -## Session-Based Authentication - -### Pattern 1: Express Session - -```typescript -import session from "express-session"; -import RedisStore from "connect-redis"; -import { createClient } from "redis"; - -// Setup Redis for session storage -const redisClient = createClient({ - url: process.env.REDIS_URL, -}); -await redisClient.connect(); - -app.use( - session({ - store: new RedisStore({ client: redisClient }), - secret: process.env.SESSION_SECRET!, - resave: false, - saveUninitialized: false, - cookie: { - secure: process.env.NODE_ENV === "production", // HTTPS only - httpOnly: true, // No JavaScript access - maxAge: 24 * 60 * 60 * 1000, // 24 hours - sameSite: "strict", // CSRF protection - }, - }), -); - -// Login -app.post("/api/auth/login", async (req, res) => { - const { email, password } = req.body; - - const user = await db.users.findOne({ email }); - if (!user || !(await verifyPassword(password, user.passwordHash))) { - return res.status(401).json({ error: "Invalid credentials" }); - } - - // Store user in session - req.session.userId = user.id; - req.session.role = user.role; - - res.json({ user: { id: user.id, email: user.email, role: user.role } }); -}); - -// Session middleware -function requireAuth(req: Request, res: Response, next: NextFunction) { - if (!req.session.userId) { - return res.status(401).json({ error: "Not authenticated" }); - } - next(); -} - -// Protected route -app.get("/api/profile", requireAuth, async (req, res) => { - const user = await db.users.findById(req.session.userId); - res.json({ user }); -}); - -// Logout -app.post("/api/auth/logout", (req, res) => { - req.session.destroy((err) => { - if (err) { - return res.status(500).json({ error: "Logout failed" }); - } - res.clearCookie("connect.sid"); - res.json({ message: "Logged out successfully" }); - }); -}); -``` - -## OAuth2 / Social Login - -### Pattern 1: OAuth2 with Passport.js - -```typescript -import passport from "passport"; -import { Strategy as GoogleStrategy } from "passport-google-oauth20"; -import { Strategy as GitHubStrategy } from "passport-github2"; - -// Google OAuth -passport.use( - new GoogleStrategy( - { - clientID: process.env.GOOGLE_CLIENT_ID!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET!, - callbackURL: "/api/auth/google/callback", - }, - async (accessToken, refreshToken, profile, done) => { - try { - // Find or create user - let user = await db.users.findOne({ - googleId: profile.id, - }); - - if (!user) { - user = await db.users.create({ - googleId: profile.id, - email: profile.emails?.[0]?.value, - name: profile.displayName, - avatar: profile.photos?.[0]?.value, - }); - } - - return done(null, user); - } catch (error) { - return done(error, undefined); - } - }, - ), -); - -// Routes -app.get( - "/api/auth/google", - passport.authenticate("google", { - scope: ["profile", "email"], - }), -); - -app.get( - "/api/auth/google/callback", - passport.authenticate("google", { session: false }), - (req, res) => { - // Generate JWT - const tokens = generateTokens(req.user.id, req.user.email, req.user.role); - // Redirect to frontend with token - res.redirect( - `${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`, - ); - }, -); -``` - -## Authorization Patterns - -### Pattern 1: Role-Based Access Control (RBAC) - -```typescript -enum Role { - USER = "user", - MODERATOR = "moderator", - ADMIN = "admin", -} - -const roleHierarchy: Record = { - [Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER], - [Role.MODERATOR]: [Role.MODERATOR, Role.USER], - [Role.USER]: [Role.USER], -}; - -function hasRole(userRole: Role, requiredRole: Role): boolean { - return roleHierarchy[userRole].includes(requiredRole); -} - -// Middleware -function requireRole(...roles: Role[]) { - return (req: Request, res: Response, next: NextFunction) => { - if (!req.user) { - return res.status(401).json({ error: "Not authenticated" }); - } - - if (!roles.some((role) => hasRole(req.user.role, role))) { - return res.status(403).json({ error: "Insufficient permissions" }); - } - - next(); - }; -} - -// Usage -app.delete( - "/api/users/:id", - authenticate, - requireRole(Role.ADMIN), - async (req, res) => { - // Only admins can delete users - await db.users.delete(req.params.id); - res.json({ message: "User deleted" }); - }, -); -``` - -### Pattern 2: Permission-Based Access Control - -```typescript -enum Permission { - READ_USERS = "read:users", - WRITE_USERS = "write:users", - DELETE_USERS = "delete:users", - READ_POSTS = "read:posts", - WRITE_POSTS = "write:posts", -} - -const rolePermissions: Record = { - [Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS], - [Role.MODERATOR]: [ - Permission.READ_POSTS, - Permission.WRITE_POSTS, - Permission.READ_USERS, - ], - [Role.ADMIN]: Object.values(Permission), -}; - -function hasPermission(userRole: Role, permission: Permission): boolean { - return rolePermissions[userRole]?.includes(permission) ?? false; -} - -function requirePermission(...permissions: Permission[]) { - return (req: Request, res: Response, next: NextFunction) => { - if (!req.user) { - return res.status(401).json({ error: "Not authenticated" }); - } - - const hasAllPermissions = permissions.every((permission) => - hasPermission(req.user.role, permission), - ); - - if (!hasAllPermissions) { - return res.status(403).json({ error: "Insufficient permissions" }); - } - - next(); - }; -} - -// Usage -app.get( - "/api/users", - authenticate, - requirePermission(Permission.READ_USERS), - async (req, res) => { - const users = await db.users.findAll(); - res.json({ users }); - }, -); -``` - -### Pattern 3: Resource Ownership - -```typescript -// Check if user owns resource -async function requireOwnership( - resourceType: "post" | "comment", - resourceIdParam: string = "id", -) { - return async (req: Request, res: Response, next: NextFunction) => { - if (!req.user) { - return res.status(401).json({ error: "Not authenticated" }); - } - - const resourceId = req.params[resourceIdParam]; - - // Admins can access anything - if (req.user.role === Role.ADMIN) { - return next(); - } - - // Check ownership - let resource; - if (resourceType === "post") { - resource = await db.posts.findById(resourceId); - } else if (resourceType === "comment") { - resource = await db.comments.findById(resourceId); - } - - if (!resource) { - return res.status(404).json({ error: "Resource not found" }); - } - - if (resource.userId !== req.user.userId) { - return res.status(403).json({ error: "Not authorized" }); - } - - next(); - }; -} - -// Usage -app.put( - "/api/posts/:id", - authenticate, - requireOwnership("post"), - async (req, res) => { - // User can only update their own posts - const post = await db.posts.update(req.params.id, req.body); - res.json({ post }); - }, -); -``` - -## Security Best Practices - -### Pattern 1: Password Security - -```typescript -import bcrypt from "bcrypt"; -import { z } from "zod"; - -// Password validation schema -const passwordSchema = z - .string() - .min(12, "Password must be at least 12 characters") - .regex(/[A-Z]/, "Password must contain uppercase letter") - .regex(/[a-z]/, "Password must contain lowercase letter") - .regex(/[0-9]/, "Password must contain number") - .regex(/[^A-Za-z0-9]/, "Password must contain special character"); - -// Hash password -async function hashPassword(password: string): Promise { - const saltRounds = 12; // 2^12 iterations - return bcrypt.hash(password, saltRounds); -} - -// Verify password -async function verifyPassword( - password: string, - hash: string, -): Promise { - return bcrypt.compare(password, hash); -} - -// Registration with password validation -app.post("/api/auth/register", async (req, res) => { - try { - const { email, password } = req.body; - - // Validate password - passwordSchema.parse(password); - - // Check if user exists - const existingUser = await db.users.findOne({ email }); - if (existingUser) { - return res.status(400).json({ error: "Email already registered" }); - } - - // Hash password - const passwordHash = await hashPassword(password); - - // Create user - const user = await db.users.create({ - email, - passwordHash, - }); - - // Generate tokens - const tokens = generateTokens(user.id, user.email, user.role); - - res.status(201).json({ - user: { id: user.id, email: user.email }, - ...tokens, - }); - } catch (error) { - if (error instanceof z.ZodError) { - return res.status(400).json({ error: error.errors[0].message }); - } - res.status(500).json({ error: "Registration failed" }); - } -}); -``` - -### Pattern 2: Rate Limiting - -```typescript -import rateLimit from "express-rate-limit"; -import RedisStore from "rate-limit-redis"; - -// Login rate limiter -const loginLimiter = rateLimit({ - store: new RedisStore({ client: redisClient }), - windowMs: 15 * 60 * 1000, // 15 minutes - max: 5, // 5 attempts - message: "Too many login attempts, please try again later", - standardHeaders: true, - legacyHeaders: false, -}); - -// API rate limiter -const apiLimiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 100, // 100 requests per minute - standardHeaders: true, -}); - -// Apply to routes -app.post("/api/auth/login", loginLimiter, async (req, res) => { - // Login logic -}); - -app.use("/api/", apiLimiter); -``` - -## Best Practices - -1. **Never Store Plain Passwords**: Always hash with bcrypt/argon2 -2. **Use HTTPS**: Encrypt data in transit -3. **Short-Lived Access Tokens**: 15-30 minutes max -4. **Secure Cookies**: httpOnly, secure, sameSite flags -5. **Validate All Input**: Email format, password strength -6. **Rate Limit Auth Endpoints**: Prevent brute force attacks -7. **Implement CSRF Protection**: For session-based auth -8. **Rotate Secrets Regularly**: JWT secrets, session secrets -9. **Log Security Events**: Login attempts, failed auth -10. **Use MFA When Possible**: Extra security layer - -## Common Pitfalls - -- **Weak Passwords**: Enforce strong password policies -- **JWT in localStorage**: Vulnerable to XSS, use httpOnly cookies -- **No Token Expiration**: Tokens should expire -- **Client-Side Auth Checks Only**: Always validate server-side -- **Insecure Password Reset**: Use secure tokens with expiration -- **No Rate Limiting**: Vulnerable to brute force -- **Trusting Client Data**: Always validate on server diff --git a/.claude/skills/bun-development/.openskills.json b/.claude/skills/bun-development/.openskills.json deleted file mode 100644 index ffb1625..0000000 --- a/.claude/skills/bun-development/.openskills.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "source": "/tmp/skill-selector-curated-3423638041", - "sourceType": "local", - "localPath": "/tmp/skill-selector-curated-3423638041/bun-development", - "installedAt": "2026-04-07T00:45:24.781Z" -} diff --git a/.claude/skills/bun-development/SKILL.md b/.claude/skills/bun-development/SKILL.md deleted file mode 100644 index 40566da..0000000 --- a/.claude/skills/bun-development/SKILL.md +++ /dev/null @@ -1,696 +0,0 @@ ---- -name: bun-development -description: "Fast, modern JavaScript/TypeScript development with the Bun runtime, inspired by [oven-sh/bun](https://github.com/oven-sh/bun)." -risk: critical -source: community -date_added: "2026-02-27" ---- - - - -# ⚡ Bun Development - -> Fast, modern JavaScript/TypeScript development with the Bun runtime, inspired by [oven-sh/bun](https://github.com/oven-sh/bun). - -## When to Use This Skill - -Use this skill when: - -- Starting new JS/TS projects with Bun -- Migrating from Node.js to Bun -- Optimizing development speed -- Using Bun's built-in tools (bundler, test runner) -- Troubleshooting Bun-specific issues - ---- - -## 1. Getting Started - -### 1.1 Installation - -```bash -# macOS / Linux -curl -fsSL https://bun.sh/install | bash - -# Windows -powershell -c "irm bun.sh/install.ps1 | iex" - -# Homebrew -brew tap oven-sh/bun -brew install bun - -# npm (if needed) -npm install -g bun - -# Upgrade -bun upgrade -``` - -### 1.2 Why Bun? - -| Feature | Bun | Node.js | -| :-------------- | :------------- | :-------------------------- | -| Startup time | ~25ms | ~100ms+ | -| Package install | 10-100x faster | Baseline | -| TypeScript | Native | Requires transpiler | -| JSX | Native | Requires transpiler | -| Test runner | Built-in | External (Jest, Vitest) | -| Bundler | Built-in | External (Webpack, esbuild) | - ---- - -## 2. Project Setup - -### 2.1 Create New Project - -```bash -# Initialize project -bun init - -# Creates: -# ├── package.json -# ├── tsconfig.json -# ├── index.ts -# └── README.md - -# With specific template -bun create