# Harjot Singh Rana - full site

> Full-stack and AI product engineer helping early-stage teams turn ideas, Figma designs, and half-built products into shipped SaaS and AI systems.

This document concatenates every public page of https://www.harjotrana.com as Markdown. Each section starts with its canonical URL. The compact guide with when-to-use notes is https://www.harjotrana.com/llms.txt.

## Contents

- Harjot Singh Rana: https://www.harjotrana.com/
- Work with Harjot Singh Rana: https://www.harjotrana.com/hire
- About Harjot Singh Rana: https://www.harjotrana.com/about
- Contact Harjot Singh Rana: https://www.harjotrana.com/contact
- harjotrana.com developer resources: API, MCP server, llms.txt: https://www.harjotrana.com/developers
- Privacy Policy: https://www.harjotrana.com/privacy-policy
- Projects: https://www.harjotrana.com/projects
- Helicon: https://www.harjotrana.com/projects/Helicon
- Moonshift: https://www.harjotrana.com/projects/Moonshift
- Reinstate: https://www.harjotrana.com/projects/Reinstate
- Locum: https://www.harjotrana.com/projects/Locum
- Brio UI: https://www.harjotrana.com/projects/Brio%20UI
- Swoopcut: https://www.harjotrana.com/projects/Swoopcut
- FixedLaunch: https://www.harjotrana.com/projects/FixedLaunch
- Writing: https://www.harjotrana.com/blog
- Helicon: A Desktop Client for Meta's Muse Code: https://www.harjotrana.com/blog/harness-locked-not-terminal-locked
- I Distilled Myself Into an Agent Skill, Then Benchmarked It Against Myself: https://www.harjotrana.com/blog/distilling-myself-into-an-agent-skill
- How I Scored 100/100 on Is Agentic by Vercel: https://www.harjotrana.com/blog/scoring-100-on-is-agentic
- Locum: Giving Grok Bot a Stand-In: https://www.harjotrana.com/blog/locum-grok-bot-provider-adapter
- The Practical Vercel Turborepo Cheatsheet: https://www.harjotrana.com/blog/turborepo-cheatsheet
- uv and Ruff: A Practical Python Tooling Cheatsheet: https://www.harjotrana.com/blog/uvx-and-ruff-cheatsheet
- From Zero to npm in 7 Hours: Building Weavestore: https://www.harjotrana.com/blog/7-hours-to-mvp-weavestore
- Building Zyflo: An Accessible, Motion-First React Library: https://www.harjotrana.com/blog/building-zyflo-ui-library
- Next.js and MDX: A Practical Setup Guide: https://www.harjotrana.com/blog/getting-started-nextjs-mdx
- Choosing a Stack for a Portfolio Site: https://www.harjotrana.com/blog/building-portfolio-website
- MVP Scope Estimator: https://www.harjotrana.com/tools/mvp-scope-estimator


---

# Harjot Singh Rana - Full-Stack and AI Product Engineer

Canonical URL: https://www.harjotrana.com/

> I build software from first idea to production.

- I ship multi-agent AI products and multi-tenant SaaS end to end, using TypeScript and Python.
- Founding engineer at [Moonshift](https://moonshift.io); raised generation success 46% → 76% for 1,200+ users.
- Full-stack engineer at SignVault.io, owning the API, permissions, and billing.

Bring me anything: an idea, a Figma design, or a half-built product. I can help scope it, ship it, or take ownership of the next stage.

- Work with me: https://www.harjotrana.com/hire
- Résumé (PDF): https://www.harjotrana.com/resume.pdf
- Email: me@harjotrana.com
- Status: Available for selected product work

## Selected work

Six systems I designed, built, and shipped. Each one links straight to the running product or the source.

### Moonshift

*Production · Founding engineer*

AI app builder that turns a prompt into a deployed full-stack app. I rebuilt the multi-agent generation pipeline, taking end-to-end success from 46.2% to 76.7% and cutting median prompt-to-live time 32%, to 14.9 minutes. 1,200+ users, 800+ apps generated.

- Detail page: https://www.harjotrana.com/projects/Moonshift
- Live: https://moonshift.io
- Stack: TypeScript, Python, Next.js, PostgreSQL, SSE, Docker

### Helicon

*Open source · Solo · MIT*

Desktop app and VS Code extension for Meta's Muse Code CLI (unofficial). One React UI across Tauri, web, and VS Code; native Windows support and WSL2 path translation; approvals and billing left exactly as the CLI sets them. 3,500+ installer downloads and 88 GitHub stars in five weeks.

- Detail page: https://www.harjotrana.com/projects/Helicon
- Code: https://github.com/HarjjotSinghh/helicon
- Live: https://helicon.sh
- Stack: Tauri 2, Rust, React, TypeScript, VS Code API

### Swoopcut

*Service + pipeline · Solo*

Launch videos for web apps, delivered in 48 hours. A Playwright recorder captures the live app in Chrome at 60 fps, then a Python renderer adds a spring camera, click ripples, motion blur, and 9:16 Shorts from the same take.

- Detail page: https://www.harjotrana.com/projects/Swoopcut
- Live: https://swoopcut.com
- Stack: Node.js, Playwright, Python, x264

### Brio UI

*Component library · Solo*

50 interaction-first React components, one file each, installed from a shadcn registry.

- Detail page: https://www.harjotrana.com/projects/Brio%20UI
- Code: https://github.com/brio-ui/brio-ui
- Live: https://brioui.com
- Stack: React 19, TypeScript, Motion

### FixedLaunch

*Service + site · Solo*

Fixed-price rescue for broken AI-built apps: a bug video and written fix plan within 48 hours.

- Detail page: https://www.harjotrana.com/projects/FixedLaunch
- Live: https://fixedlaunch.com
- Stack: Next.js, TypeScript, Neon, PostHog

### Reinstate

*Open source · Solo · Apache-2.0*

Go CLI that keeps Claude Code and Codex sessions alive across machines. Zero-knowledge age encryption into your own S3/R2 bucket, plus OS-aware path remapping so resume still works after switching from Windows to macOS. 23/23 cross-platform acceptance tests.

- Detail page: https://www.harjotrana.com/projects/Reinstate
- Code: https://github.com/HarjjotSinghh/reinstate
- Live: https://reinstate.dev
- Stack: Go, age, S3, GitHub Actions

### Locum

*Open source · Solo · Apache-2.0*

Python MCP server that lets Grok Bot hand coding jobs to Claude Code and Codex on your own machine. Built-in OAuth 2.1 with PKCE, an async job queue that survives restarts, and spend caps.

- Detail page: https://www.harjotrana.com/projects/Locum
- Code: https://github.com/HarjjotSinghh/locum
- Stack: Python, MCP, Claude Code, Codex, Cloudflare

All projects: https://www.harjotrana.com/projects

## Experience

### [The Whiskey Library](https://whiskeylibrary.com) | E-Commerce Full-Stack Developer (Aug 2026 - Present)

Build the Shopify storefront, Recharge subscription backend, subscriber dashboard, and taste-profile quiz.

### [Moonshift](https://moonshift.io) | Founding Engineer (Jan 2026 - Aug 2026)

Built and shipped the AI app builder end to end - multi-agent pipeline, SSE observability, prompt-to-deploy - to 1,200+ users.

### SignVault.io | Full-Stack Software Engineer (Nov 2024 - Nov 2025)

Owned the e-signature REST API, ABAC engine, Stripe billing, and document workflows for a multi-tenant SaaS.

### [LawSikho](https://lawsikho.com) | Software Engineer Intern (Sep 2024 - Nov 2024)

Multithreaded Python pipeline that scraped and deduplicated 100,000+ job listings, deployed on AWS EC2.

### [Mindcase](https://mindcase.co) | Full-Stack Engineer Intern (Aug 2024 - Sep 2024)

Built an AI blog-generation platform on the OpenAI and Reddit APIs, from interface to content preview.

Full résumé: https://www.harjotrana.com/resume.pdf

## Education

- B.Tech, Computer Science and Engineering, Guru Tegh Bahadur Institute of Technology (GGSIPU, New Delhi), 2022 - 2026

## Recognition

- Avalanche ecosystem mini-grant: $10K. One of 10 teams selected across India.
- OpenCode Buildathon: Top 120 of 20,000 participants.
- [GitHub Universe 2025](https://zyflo.vercel.app): Zyflo showcased during the event.
- [LeetCode](https://leetcode.com/u/HarjjotSinghh): 770+ problems solved.

## Writing

- [Helicon: A Desktop Client for Meta's Muse Code](https://www.harjotrana.com/blog/harness-locked-not-terminal-locked) (16 Sep 2026, 19 min): Meta's Muse Code is terminal-only, has no Windows binary, and only bills your subscription through its own harness. The desktop client I built for it, and why that last fact decided everything.
- [I Distilled Myself Into an Agent Skill, Then Benchmarked It Against Myself](https://www.harjotrana.com/blog/distilling-myself-into-an-agent-skill) (10 Sep 2026, 25 min): I mined 1,398 judgment events out of five coding agents' session logs and turned them into a portable skill. Then a control model, the skill and I answered the same ten decisions blind.
- [How I Scored 100/100 on Is Agentic by Vercel](https://www.harjotrana.com/blog/scoring-100-on-is-agentic) (23 Aug 2026, 21 min): My portfolio scored 75 on Vercel's agent-readiness scanner. Two rounds later it scored 100. Every change that moved the number, including the one header Next.js would not let me set.

All writing: https://www.harjotrana.com/blog

## Stack

- **Core**: TypeScript, Python, React, Next.js, Node.js, PostgreSQL
- **Backend**: NestJS, Express, FastAPI, Redis, SSE, GraphQL
- **AI systems**: Anthropic, OpenAI, LangChain
- **Commerce**: Shopify, Recharge, Klaviyo, PostHog, Stripe
- **Infrastructure**: Cloudflare Workers, Vercel, AWS, Docker, Drizzle, Supabase
- **Also**: Tauri, Rust, Go, Bun, MongoDB, Socket.IO

## Open-source activity

GitHub contribution graph for the last 12 months: https://github.com/HarjjotSinghh

## Contact

Building something and need a product engineer? Email me@harjotrana.com or use the form at https://www.harjotrana.com/hire#start. Share the product, the stage, and what you need next. I reply within one business day.

- GitHub: https://github.com/HarjjotSinghh
- LinkedIn: https://www.linkedin.com/in/HarjjotSinghh
- Twitter: https://x.com/HarjjotSinghh
- LeetCode: https://leetcode.com/u/HarjjotSinghh
- Behance: https://www.behance.net/harjjot

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Work with Harjot Singh Rana - Product engineering for early-stage teams

Canonical URL: https://www.harjotrana.com/hire

> Bring me the idea. I'll help you ship the product.

I'm Harjot, a full-stack engineer and AI engineer. If you have a Figma, a rough brief, or a half-built product, I'll help turn it into something real, live, and maintainable. I built Moonshift, a multi-agent app builder that served 1,200+ users, so I know the gap between a demo and a product people can actually use.

Open to contract, freelance, and full-time roles · Remote from New Delhi (IST) · me@harjotrana.com · Replies within one business day

- Start a project: https://www.harjotrana.com/hire#start
- Book a call: https://cal.com/harjot
- Email: me@harjotrana.com

## What are you trying to ship?

The best fit is a real product problem with a decision-maker, a useful first milestone, and a willingness to keep scope honest.

- **You have an idea**: You need a small, useful first version and a technical partner who can help decide what not to build yet.
- **You have a product**: You need an AI feature, integration, reliability pass, or another pair of hands to move the roadmap forward.
- **Your codebase is stuck**: The prototype works in a demo but is slow, fragile, insecure, or difficult to take to production.

## Ways I can help

Every engagement starts with a written scope. The right shape and price depend on the product, the deadline, and what already exists.

### Idea to live MVP

*For founders without a technical co-founder*

Scope the smallest useful product, build the core experience, connect the backend, and deploy it with a handover you can own.

Shape: Fixed-scope sprint · timeline depends on scope

### AI product sprint

*For teams adding AI to an existing product*

Turn an LLM, agent, RAG, or automation idea into a working feature with sensible evaluation, observability, and cost decisions.

Shape: Fixed-scope sprint · timeline depends on scope

### Product or codebase rescue

*For half-built or AI-generated products*

Find the highest-risk problems, make the next fixes concrete, and create a path from fragile prototype to shippable product.

Shape: Diagnostic first · implementation quoted separately

### Fractional product engineering

*For small teams that need ownership*

Take responsibility for a product area, ship continuously, and give a founder or product team dependable technical momentum without adding a full-time hire immediately.

Shape: Ongoing monthly engagement · explicit capacity

## Proof of shipped systems

These are the systems and outcomes I can show, not a list of tools I have tried.

- **[Moonshift](https://moonshift.io)**: Founding engineer on an AI app builder for 1,200+ users. Improved end-to-end generation success from 46.2% to 76.7% and reduced median prompt-to-live time by 32%.
- **[Helicon](https://helicon.sh)**: Built an open-source desktop app and VS Code extension for the Muse Code CLI: 3,500+ installer downloads and 88 GitHub stars in five weeks.
- **[Swoopcut](https://swoopcut.com)**: Built a launch-video pipeline for web apps: a Playwright recorder captures the live app at 60 fps, and a Python renderer adds camera motion and 9:16 cuts.
- **[Brio UI](https://brioui.com)**: Built an open-source library of 50 interaction-first React components, one file each, installed from a shadcn registry.

Project details: https://www.harjotrana.com/projects

## A straightforward process

1. **Understand the problem**: A short call or written brief clarifies the user, desired outcome, constraints, urgency, and what success means.
2. **Write the smallest useful scope**: You receive a clear proposal with deliverables, exclusions, milestones, dependencies, and a fixed price or monthly arrangement.
3. **Build in visible increments**: Work is shipped in small steps with a written update each week and decisions recorded when scope changes.
4. **Launch and hand over**: The product is deployed to your infrastructure, with documentation, a walkthrough, and an agreed support window.
5. **Decide what comes next**: We close the loop with the next product priorities, a maintenance plan, or a clean handoff to your team.

## Before we start

### How does remote collaboration work?

I work remotely from New Delhi (IST). We agree on overlap for important decisions, use written updates for the rest, and keep dependencies visible so timezone differences do not become a surprise.

### How are scope and payments handled?

The proposal defines what is included, what is not, milestones, payment timing, ownership, and support. Changes are quoted separately instead of being hidden in an open-ended invoice.

### What if I am not sure what to build?

That is fine. Share the idea, the customer, and what you have tried. I can help turn it into a smaller first milestone before we commit to a build.

### Are you available for contract work and full-time roles?

Yes. I take contract and freelance engagements as a full-stack engineer and AI engineer, and I am open to full-time roles. Client work starts with a written scope on this page. For full-time hiring, email me@harjotrana.com or book a call; the [resume](https://www.harjotrana.com/resume.pdf) has the fuller employment history.

## Tell me what you are building

Share the product, its stage and the outcome you want. I reply with a next step within one business day.

- Inquiry form: https://www.harjotrana.com/hire#start
- MVP Scope Estimator: https://www.harjotrana.com/tools/mvp-scope-estimator
- Book a call: https://cal.com/harjot
- Email: mailto:me@harjotrana.com?subject=Project%20inquiry

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# About Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/about

> A full-stack engineer and AI engineer in New Delhi, open to contract and full-time work, who takes early-stage products from a rough idea, a Figma, or a half-built codebase to something live and maintainable.

## Who I am

Harjot Singh Rana is a full-stack engineer and AI engineer based in New Delhi, India. He is available for contract, freelance, and full-time roles, remotely from India.

I'm Harjot Singh Rana. I work with TypeScript and Python, and I ship multi-agent AI products, multi-tenant SaaS, real-time backends, and developer tooling end to end: the product surface, the API, the data model, the billing, and the unglamorous production hardening in between.

This site is the canonical public record of that work. Every claim on it resolves to something you can open: a live product, a repository, a package, a post, or the [résumé](/resume.pdf).

## What I have shipped

- [The Whiskey Library](https://whiskeylibrary.com): e-commerce full-stack developer since August 2026. I build the Shopify storefront, the subscription backend on Recharge, and customer tools such as the subscription dashboard and the taste-profile quiz.
- [Helicon](https://helicon.sh): an open-source desktop app and VS Code extension for Meta's Muse Code CLI (unofficial). 3,500+ installer downloads and 88 GitHub stars within five weeks of the first commit.
- [Moonshift](https://moonshift.io): founding engineer on an AI app builder. I rebuilt the multi-agent generation pipeline, raising end-to-end success from 46.2% to 76.7% and cutting median prompt-to-live time 32%, for 1,200+ users generating 800+ apps.
- [Reinstate](https://reinstate.dev): an open-source Go CLI for encrypted, cross-machine Claude Code and Codex session continuity, with 23/23 cross-platform acceptance tests passing.
- [FixedLaunch](https://fixedlaunch.com): a fixed-price rescue service for broken AI-built apps. Founders get a bug video and a written fix plan within 48 hours of access.
- [Brio UI](https://brioui.com), a library of 50 interaction-first React components, and [Swoopcut](https://swoopcut.com), a pipeline that records a live web app and renders a 60 fps launch clip.

The full list, with stack and outcomes, is on the [projects page](/projects).

## Background

B.Tech in Computer Science and Engineering from Guru Tegh Bahadur Institute of Technology (GGSIPU, New Delhi), 2022 to 2026. Before Moonshift I was a full-stack engineer at SignVault.io, a multi-tenant e-signature SaaS where I owned the REST API, the ABAC permissions engine, and Stripe billing. Earlier I was a software engineering intern at LawSikho, where I built a multithreaded Python pipeline that scraped and deduplicated 100,000+ job listings, and a full-stack intern at Mindcase.

Recognition that other people can check: a $10K Avalanche ecosystem mini-grant (one of 10 teams selected across India), a top-120 finish out of 20,000 participants in the OpenCode Buildathon, and 770+ problems solved on [LeetCode](https://leetcode.com/u/HarjjotSinghh).

## How I work

I work remotely with founders, operators, and small product teams on contract, freelance, full-time, and fractional engagements. Every engagement starts with a written scope that says what is included, what is not, and what happens when that changes. The ways I can help, and the process, are described on the [work with me](/hire) page.

Questions, introductions, or a project brief: see the [contact page](/contact).


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Contact Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/contact

> Email is the most reliable route. Share the product, the stage it is at, and what you need next, and I will reply within one business day.

## Direct channels

- Email: [me@harjotrana.com](mailto:me@harjotrana.com)
- Book a call: [cal.com/harjot](https://cal.com/harjot)
- Project inquiry form: [harjotrana.com/hire#start](/hire#start)

I work remotely from New Delhi, India (UTC+5:30). For time-sensitive decisions we agree on an overlap window; everything else runs on written updates.

## What to include

A few details are enough to start a useful conversation. You do not need a finished spec.

- What the product is and who it is for.
- The current stage: an idea, a Figma, a prototype, or a product in production.
- The outcome you want next and when it matters.
- Anything already built, with links if they exist.

Still shaping the first release? The [MVP Scope Estimator](/tools/mvp-scope-estimator) turns a rough idea into a clearer first milestone you can paste into your message.

## Public profiles

- GitHub: [github.com/HarjjotSinghh](https://github.com/HarjjotSinghh)
- LinkedIn: [linkedin.com/in/HarjjotSinghh](https://www.linkedin.com/in/HarjjotSinghh)
- X (Twitter): [x.com/HarjjotSinghh](https://x.com/HarjjotSinghh)
- LeetCode: [leetcode.com/u/HarjjotSinghh](https://leetcode.com/u/HarjjotSinghh)
- Behance: [behance.net/harjjot](https://www.behance.net/harjjot)

## For AI agents and automated tools

Machine-readable entry points are listed on the [developer resources](/developers) page and in [llms.txt](/llms.txt). Contact requests on behalf of a person should go through email or the inquiry form above; do not submit credentials, secrets, or sensitive personal information through any channel on this site.


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# harjotrana.com developer resources: API, MCP server, llms.txt

Canonical URL: https://www.harjotrana.com/developers

> Every machine-readable surface of harjotrana.com in one place, with the URL, the format, and what it is for. Everything here is public, read-only, and needs no key or account.

## Start here

- [llms.txt](/llms.txt): the AI-oriented site guide, including a "When to use this site" section and how agents should call it.
- [llms-full.txt](/llms-full.txt): every public page of harjotrana.com rendered as one Markdown document.
- [OpenAPI description](/openapi.json): OpenAPI 3.1 document for every endpoint on this page, with typed response schemas and the error model.
- [API index](/api/v1): JSON list of the v1 endpoints with their documentation links.
- [API catalog](/.well-known/api-catalog): RFC 9727 linkset pointing at the OpenAPI description and this page.
- [sitemap.xml](/sitemap.xml) and [robots.txt](/robots.txt): the canonical HTML pages and crawler policy.

## REST API (harjotrana.com /api/v1)

A read-only JSON API over the same facts the HTML pages show. Base URL `https://www.harjotrana.com/api/v1`. Authentication: none. Every response is `application/json; charset=utf-8`, CORS-open (`Access-Control-Allow-Origin: *`), cacheable, and carries a `Link` header to the OpenAPI description and this page.

- `GET /api/v1`: index of endpoints with documentation links.
- `GET /api/v1/profile`: who Harjot is, with email, profiles, stack, and page URLs.
- `GET /api/v1/services`: engagement types, process, and FAQ. No prices are published.
- `GET /api/v1/projects` and `GET /api/v1/projects/{name}`: all projects, then one project with case study and features (name is case-insensitive, e.g. `Moonshift`).
- `GET /api/v1/posts` and `GET /api/v1/posts/{slug}`: technical writing, then one post with its Markdown body.
- `GET /api/v1/search?q=reinstate&limit=5`: keyword search across every public page.
- `GET /api/v1/contact-options`: email, booking link, inquiry form, and what to include.
- `GET /api/contributions`: last twelve months of GitHub contribution days.

Example: `curl -H "Accept: application/json" https://www.harjotrana.com/api/v1/projects/Moonshift` returns the Moonshift case study as JSON. Only `GET`, `HEAD`, and `OPTIONS` are supported; other methods return a `405` problem with an `Allow` header. There are no write endpoints, webhooks, or OAuth flows: to contact or hire Harjot, hand the user the links from `/api/v1/contact-options`.

## Errors

Every 4xx and 5xx from `/api/*`, `/mcp`, the discovery documents, and content negotiation is an RFC 9457 problem-details object served as `application/problem+json`. Members: `type` (a URI that resolves to the matching entry below), `title`, `status`, `detail`, `instance` (the request path), plus two extension members: `code`, a stable machine-readable identifier, and `hint`, what to do next. Validation failures add `invalidParams`.

- **bad_request (400)**: A query or path parameter is missing or invalid; `invalidParams` names it. Fix the request.
- **not_found (404)**: No endpoint or resource at that path. The `hint` names where to list valid ones. Unknown `/api/*` paths always return this, never an HTML page.
- **method_not_allowed (405)**: The endpoint is read-only. The `Allow` header lists supported methods.
- **not_acceptable (406)**: The `Accept` header on a page request allowed neither `text/html` nor `text/markdown`; `available` lists both.
- **rate_limited (429)**: Quota exhausted. Wait `Retry-After` seconds, then retry; reuse cached responses instead of re-fetching.
- **internal_error (500)**: The server failed to produce a response. Retry once; if it persists, email me@harjotrana.com with the URL.
- **upstream_unavailable (502)**: A third-party source the endpoint depends on (GitHub for `/api/contributions`) did not answer. Retry in a few minutes.

The MCP endpoint keeps JSON-RPC semantics: a malformed body is a JSON-RPC `-32700` error object with HTTP 400, unknown methods are `-32601`, and tool-level failures set `isError: true` on the result.

## Rate limits

`/api/*`, `/mcp`, and the discovery documents allow 120 requests per 60 seconds per client address. Every response carries the IETF RateLimit header fields (draft-ietf-httpapi-ratelimit-headers): `RateLimit-Policy: "default";q=120;w=60` declares the quota, and `RateLimit: "default";r=<remaining>;t=<seconds until reset>` reports where you stand. Exceeding it returns a `429` problem with `Retry-After`, which takes precedence over `RateLimit`.

The counter is per server instance, so treat the headers as a live advisory signal rather than a single global cap. Text documents (`/llms.txt`, `/llms-full.txt`, `/feed.xml`, `/sitemap.xml`) and HTML pages are outside the policy.

## Versioning and deprecation

The REST API is versioned in the path: `/api/v1`. Additive changes (new fields, new endpoints) ship in place; breaking changes ship under a new prefix (`/api/v2`) and never alter v1 responses. The MCP server negotiates its protocol version on `initialize` and advertises supported versions in its server card.

Deprecation policy: an endpoint or version scheduled for removal announces it at least 90 days in advance with the `Deprecation` response header (RFC 9745) and a `Sunset` header (RFC 8594) giving the removal date, and is listed here with its replacement. Nothing is deprecated today.

## MCP server

harjotrana.com runs a read-only Model Context Protocol server over Streamable HTTP at [https://www.harjotrana.com/mcp](/mcp). It exposes the portfolio as tools: profile, services, projects, writing, search, and contact options. No authentication, no session state, no side effects.

- Endpoint: `POST https://www.harjotrana.com/mcp` (JSON-RPC 2.0, Streamable HTTP transport, protocol versions 2025-03-26, 2025-06-18, and 2025-11-25). Responses are `application/json`; the server never opens server-initiated streams, so `GET /mcp` returns a `405` problem as the transport spec allows. The legacy HTTP+SSE transport is not offered.
- Server card: [/mcp/server-card](/mcp/server-card) (`application/mcp-server-card+json`, SEP-2127), mirrored with connection hints at [/.well-known/mcp.json](/.well-known/mcp.json) and listed in [/.well-known/ai-catalog.json](/.well-known/ai-catalog.json).
- Tools: `get_profile`, `get_services`, `list_projects`, `get_project`, `list_posts`, `get_post`, `get_contact_options`, `search_site`. Resources: llms.txt, llms-full.txt, openapi.json.
- Client config: `{ "mcpServers": { "harjotrana": { "type": "http", "url": "https://www.harjotrana.com/mcp" } } }`.

## Markdown content negotiation

Every public page on harjotrana.com also has a Markdown representation, following the acceptmarkdown.com convention. Send `Accept: text/markdown` to any page URL and you get `Content-Type: text/markdown; charset=utf-8` with `Vary: Accept`. Appending `.md` to a page path (for example `/blog/turborepo-cheatsheet.md`, or `/index.md` for the home page) returns Markdown unconditionally. Requests that accept neither HTML nor Markdown receive a `406` problem. Unknown paths return a real `404` with a short Markdown body pointing back to the sitemap and llms.txt.

## Usage policy

All of the above is free to read and cache. The HTML pages are canonical; JSON, Markdown, llms.txt, and MCP responses are derived from the same source files at build time and must not be quoted as if they contained facts the HTML does not. Do not infer employment availability, client relationships, testimonials, pricing, or outcomes beyond what the pages state. Questions about these resources: [me@harjotrana.com](mailto:me@harjotrana.com).


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Privacy Policy

Canonical URL: https://www.harjotrana.com/privacy-policy

> Privacy and data-handling information for harjotrana.com.

Effective date · 07 August 2026

## 1. Analytics

This website uses PostHog for explicit product-analytics events and Vercel Analytics and Speed Insights for aggregate traffic and performance diagnostics. PostHog is configured with its EU host.

- Page views and referring/campaign information
- Opted-in navigation, CTA, project, resume, and calendar clicks
- Form-start, submission, failure, and safe qualification events
- Aggregate tool-start, completion, copy, reset, and handoff events without raw tool answers

DOM-wide autocapture and session replay are disabled. The analytics events do not include names, email addresses, message bodies, or other raw contact-form values, estimator selections, or generated scope briefs.

## 2. Contact form

If you submit the project form, the information you provide, such as your email address, product context, and message, is used to respond to your inquiry and discuss whether the work is a fit.

Do not submit confidential credentials, secrets, or sensitive personal information through the form.

## 3. Portfolio and project pages

The website [www.harjotrana.com](https://www.harjotrana.com) is a personal portfolio for sharing software projects, technical writing, and a resume.

The separate User Data Puller demonstration is an educational utility and is not part of the indexable portfolio funnel.

## 4. Automated and agent access

Public pages, the Markdown representations served through content negotiation, llms.txt, the RSS feed, the sitemap, and the read-only MCP server expose only information that is already published on this site. These endpoints do not set cookies, do not require accounts, and do not collect personal information beyond standard server request logs.

## 5. Contact information

If you have any questions regarding this privacy policy, feel free to contact me at:

Email: [me@harjotrana.com](mailto:me@harjotrana.com)


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Projects by Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects

> Production work, open-source tools, and experiments across AI, SaaS, developer tooling, and real-time systems. Every entry links to the running product or the source.

## Production & open source

### Moonshift

*Production · Founding engineer*

AI app builder that turns a prompt into a deployed full-stack app. I rebuilt the multi-agent generation pipeline, taking end-to-end success from 46.2% to 76.7% and cutting median prompt-to-live time 32%, to 14.9 minutes. 1,200+ users, 800+ apps generated.

- Detail page: https://www.harjotrana.com/projects/Moonshift
- Live: https://moonshift.io
- Stack: TypeScript, Python, Next.js, PostgreSQL, SSE, Docker

### Helicon

*Open source · Solo · MIT*

Desktop app and VS Code extension for Meta's Muse Code CLI (unofficial). One React UI across Tauri, web, and VS Code; native Windows support and WSL2 path translation; approvals and billing left exactly as the CLI sets them. 3,500+ installer downloads and 88 GitHub stars in five weeks.

- Detail page: https://www.harjotrana.com/projects/Helicon
- Code: https://github.com/HarjjotSinghh/helicon
- Live: https://helicon.sh
- Stack: Tauri 2, Rust, React, TypeScript, VS Code API

### Swoopcut

*Service + pipeline · Solo*

Launch videos for web apps, delivered in 48 hours. A Playwright recorder captures the live app in Chrome at 60 fps, then a Python renderer adds a spring camera, click ripples, motion blur, and 9:16 Shorts from the same take.

- Detail page: https://www.harjotrana.com/projects/Swoopcut
- Live: https://swoopcut.com
- Stack: Node.js, Playwright, Python, x264

### Brio UI

*Component library · Solo*

50 interaction-first React components, one file each, installed from a shadcn registry.

- Detail page: https://www.harjotrana.com/projects/Brio%20UI
- Code: https://github.com/brio-ui/brio-ui
- Live: https://brioui.com
- Stack: React 19, TypeScript, Motion

### FixedLaunch

*Service + site · Solo*

Fixed-price rescue for broken AI-built apps: a bug video and written fix plan within 48 hours.

- Detail page: https://www.harjotrana.com/projects/FixedLaunch
- Live: https://fixedlaunch.com
- Stack: Next.js, TypeScript, Neon, PostHog

### Reinstate

*Open source · Solo · Apache-2.0*

Go CLI that keeps Claude Code and Codex sessions alive across machines. Zero-knowledge age encryption into your own S3/R2 bucket, plus OS-aware path remapping so resume still works after switching from Windows to macOS. 23/23 cross-platform acceptance tests.

- Detail page: https://www.harjotrana.com/projects/Reinstate
- Code: https://github.com/HarjjotSinghh/reinstate
- Live: https://reinstate.dev
- Stack: Go, age, S3, GitHub Actions

### Locum

*Open source · Solo · Apache-2.0*

Python MCP server that lets Grok Bot hand coding jobs to Claude Code and Codex on your own machine. Built-in OAuth 2.1 with PKCE, an async job queue that survives restarts, and spend caps.

- Detail page: https://www.harjotrana.com/projects/Locum
- Code: https://github.com/HarjjotSinghh/locum
- Stack: Python, MCP, Claude Code, Codex, Cloudflare


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Helicon - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Helicon

> Free, MIT-licensed desktop app (Windows, macOS, Linux) and VS Code extension for Meta's Muse Code CLI. Projects, sessions, readable approvals, inline diffs, and the real plan meter in one UI, with native Windows support and WSL2 path translation. Unofficial, not affiliated with Meta.

## Links

- GitHub: https://github.com/HarjjotSinghh/helicon
- Live: https://helicon.sh

## Case study

### Situation

Muse Code ships as a CLI. Developers who prefer a desktop app, as with Claude Code or Codex, had no client, and Windows users had to fall back to WSL.

### Constraint

The client had to stay unofficial and honest: no bundled credentials, no bypassed approvals or billing, and nothing that implied Meta endorsement, while still matching the CLI's real behaviour on three operating systems.

## Stack

Tauri 2, React, TypeScript, Rust, VS Code API

## What I built

- Built a shared React UI running in a Tauri 2 shell, the browser, and a VS Code webview.
- Wrapped `muse serve` so sessions, approvals, goals, and usage map to the CLI one-to-one.
- Added native Windows and WSL2 path translation, plus CI that builds every installer.

## Outcome

Shipped v0.22.0 with 3,500+ installer downloads and 88 GitHub stars in five weeks, listed on VS Code Marketplace and Open VSX.

- Role: Creator and maintainer
- Timeline: September 2026 · ongoing
- Technical detail: Tauri 2; Rust; React; TypeScript; VS Code extension API; Muse Code CLI (muse serve); GitHub Actions.

Discuss a developer-tool build: https://www.harjotrana.com/hire

## Key features

- Projects grouped by working directory, including isolated worktrees
- Session history with resume and diffs shown where the edit happened
- Readable approval prompts that follow the CLI's own approval rules
- Plan meter showing the 5-hour window and weekly cap
- File viewer beside the thread for source, Markdown, images, video, and PDFs
- Goal panel to set, pause, resume, and clear a /goal
- Background tool calls you can stop, cancel, skip, or retry
- Remote daemon so the web UI can drive muse on another machine

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Moonshift - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Moonshift

> AI app builder that turns prompts into deployed full-stack applications. Multi-agent generation pipeline, SSE real-time observability, and production usage by 1,200+ users generating 800+ apps.

## Links

- Live: https://moonshift.io

## Case study

### Situation

Moonshift helps founders and developers turn a natural-language idea into a launched product on their own infrastructure. A successful run had to produce more than a demo: a real app, a GitHub repository, a Vercel deployment, and a launch kit.

### Constraint

Multiple agents and external side effects had to work together without hiding cost, reliability, or publishing decisions from the user. Deployment and social publishing needed explicit approval gates.

## Stack

TypeScript, Python, Next.js, PostgreSQL, Node.js, OpenAI, Docker

## What I built

- Rebuilt the multi-agent pipeline around research, planning, validation, and fixer loops.
- Added live SSE observability for phases, agent state, assets, deploys, and approvals.
- Added cost caps, resumable runs, and GitHub, Vercel, Turso, Playwright, Docker hooks.

## Outcome

Raised end-to-end generation success from 46.2% to 76.7%, cut median prompt-to-live time from 22.1 to 14.9 minutes, and supported 1,200+ users generating 800+ apps. The run dashboard streamed 7.3M+ SSE events across 2,000+ runs.

- Role: Founding Engineer
- Timeline: April–July 2026
- Technical detail: Bun + TypeScript monorepo; Next.js dashboard; orchestrator-v2; SSE; Drizzle/Turso; Vercel; GitHub; Playwright; Docker.

Discuss a similar product: https://www.harjotrana.com/hire

## Key features

- Prompt-to-deploy full-stack app generation
- Multi-agent LLM orchestration pipeline
- Real-time SSE observability at multi-million event scale
- Production use: 1,200+ users, 800+ generated apps

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Reinstate - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Reinstate

> Open-source Go CLI for coding-agent session continuity. Finds and resumes Claude Code and Codex sessions locally, and syncs them across macOS, Windows, Linux, and WSL2 through client-side age encryption and your own S3-compatible storage—with OS-aware path remapping so resume works after a machine switch.

## Links

- GitHub: https://github.com/HarjjotSinghh/reinstate
- Live: https://reinstate.dev

## Case study

### Situation

Reinstate addresses a specific gap in AI-assisted development: the code survives a device switch, but the agent's working context does not. A session started on one machine should be findable and resumable on another without leaving the agent's local storage behind.

### Constraint

Agent sessions are vendor-specific and contain absolute paths, sensitive files, and state that should not be copied blindly. Sync needed to preserve native resume while handling path remapping, credential exclusion, conflicts, backups, and offline use.

## Stack

Go, age, S3, GitHub Actions

## What I built

- Built Claude Code and Codex adapters that find sessions and restore them natively.
- Added client-side age encryption and your own R2/S3 bucket, so it holds only ciphertext.
- Hardened restore: path remapping, credential denylist, dry runs, atomic writes, backups.

## Outcome

Passed 23/23 Mac–Windows acceptance tests for path remapping and conflict-safe restore. The result is an Apache-2.0 CLI that keeps the user's bucket, keys, and agent sessions under their control.

- Role: Creator and maintainer
- Timeline: July 2026 · ongoing
- Technical detail: Go; Cobra; age; S3-compatible storage; OS keyring; path mapping; atomic restore; GitHub Actions.

Discuss a systems build: https://www.harjotrana.com/hire

## Key features

- Configless local index and search across sessions, prompts, files, projects
- Native resume, last, and fork for Claude Code and Codex
- Encrypted push/pull via age and user-owned S3-compatible storage
- OS-aware path remapping so --resume works after switching machines
- Pre-launch checks for workspace, agent layout, and capabilities
- Interactive TTY switcher with deterministic JSON for automation
- Read-only discovery for Gemini CLI and OpenCode sessions
- Apache-2.0: no Reinstate account; your bucket, your keys

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Locum - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Locum

> Open-source Python MCP server that lets Grok Bot hand coding jobs to Claude Code and Codex running on your own machine. It ships its own OAuth 2.1 authorization server, an async job queue with a persistent journal, and spend and volume guardrails, and is reached over a Cloudflare tunnel as a Grok custom connector.

## Links

- GitHub: https://github.com/HarjjotSinghh/locum
- Write-up: https://www.harjotrana.com/blog/locum-grok-bot-provider-adapter

## Case study

### Situation

Grok Bot can call custom MCP connectors, but it has no way to run code on a developer's own machine. Locum fills that gap so Grok can delegate a coding job to the Claude Code or Codex install you already use.

### Constraint

The server is reachable from the public internet through a tunnel and can start coding agents on a personal machine. Access had to be gated by real authorization, and runaway jobs had to be capped.

## Stack

Python, MCP, Claude Code, Codex, Cloudflare

## What I built

- Built the MCP server in Python with 8 tools covering the job lifecycle.
- Wrote an OAuth 2.1 authorization server with PKCE and a passphrase consent gate instead of relying on the tunnel for access control.
- Added an async queue with a persistent journal, plus spend and volume guardrails.

## Outcome

A single Python package that connects Grok Bot to local coding agents with authorization, restart-safe job tracking, and spend limits. Released under Apache-2.0.

- Role: Creator and maintainer
- Timeline: Aug 2026 · Sep 2026
- Technical detail: Python 3.11+; MCP; OAuth 2.1 with PKCE; async job queue with a persistent journal; Cloudflare tunnel; GitHub Actions.

Discuss an agent integration: https://www.harjotrana.com/hire

## Key features

- 8 MCP tools for starting, watching, and stopping coding jobs
- Built-in OAuth 2.1 authorization server with PKCE and passphrase consent
- Async job queue with a persistent journal that survives restarts
- Spend and volume guardrails per job and per day
- --doctor command that checks the setup before you connect
- Dashboard for live job status
- Works with Claude Code and Codex on your own machine
- Apache-2.0; independent project, not affiliated with xAI, OpenAI, Anthropic, or Anysphere

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Brio UI - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Brio%20UI

> Interaction-first React component library for the parts every website needs: forms, pricing, consent, changelogs, 404s, usage, and inboxes. Each component is one self-contained file you can copy into any React 19 project, themeable through library tokens.

## Links

- GitHub: https://github.com/brio-ui/brio-ui
- Live: https://brioui.com

## Stack

React 19, TypeScript, Motion, Vite

## What I built

- Designed and built 50 composed React components for everyday website sections.
- Single-file components: styles injected with React 19 style hoisting, no CSS build step.
- Runtime dependencies limited to react, react-dom, and motion.
- Shadcn-compatible registry so components install with one command.
- Scaffolding script that creates, registers, and routes a new component.

## Key features

- Higher-level patterns on top of base primitives, not another button library
- Neutral default variant that fits a bank, a dashboard, or a blog
- Theme tokens shared across every component
- Playground with a live demo for each component
- Copy-paste install or shadcn registry install

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Swoopcut - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/Swoopcut

> Pipeline that turns a live web app into a launch-ready demo clip: real Chrome capture at 60 fps, a spring camera that follows the cursor, click ripples and sound, motion blur, and 9:16 Shorts from the same take.

## Links

- Live: https://swoopcut.com

## Stack

Node.js, Python, x264 / FFmpeg, Swift

## What I built

- Built a Playwright recorder capturing CDP screencast frames at 2x plus input events.
- Wrote a single-pass Python renderer for window, cursor, ripples, camera, motion blur.
- Renders 1080p60 with x264 and cuts 9:16 Shorts from the same recorded take.
- Produced the launch clip for Helicon's /try page.

## Key features

- Scripted flows replayed in headed Chrome at 60 fps
- Spring camera that zooms the whole frame and follows the cursor
- Click ripples, click sounds, and motion blur
- Vertical Shorts generated from horizontal takes

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# FixedLaunch - Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/projects/FixedLaunch

> Fixed-price rescue service for broken AI-built apps. Founders send access to a Lovable, Bolt, v0 or Cursor app and get a video of the bug and a written fix plan within 48 hours, then an optional fix sprint.

## Links

- Live: https://fixedlaunch.com

## Stack

Next.js, TypeScript, Tailwind CSS, Neon Postgres, PostHog, Vercel

## What I built

- Designed the offer, pricing, and delivery for a fixed-price diagnosis and fix sprint.
- Built the Next.js 16 site with a request form backed by Neon Postgres and an alert API.
- Wrote a Launch-Check Kit: 24 checks, fix prompts, an error decoder, and a bug template.
- Published guides plus llms.txt and agents.md pages so AI assistants can read the offer.

## Key features

- Bug video and written fix plan within 48 hours of access
- No passwords needed to start a diagnosis
- Covers apps built with Lovable, Bolt, v0, Replit, and Cursor
- Diagnosis fee credited toward the fix sprint

All projects: https://www.harjotrana.com/projects

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Writing by Harjot Singh Rana

Canonical URL: https://www.harjotrana.com/blog

> Technical notes on AI systems, developer tooling, full-stack architecture, and shipping production software.

RSS feed: https://www.harjotrana.com/feed.xml

## [Helicon: A Desktop Client for Meta's Muse Code](https://www.harjotrana.com/blog/harness-locked-not-terminal-locked)

16 Sep 2026 · 19 min read

Meta's Muse Code is terminal-only, has no Windows binary, and only bills your subscription through its own harness. The desktop client I built for it, and why that last fact decided everything.

## [I Distilled Myself Into an Agent Skill, Then Benchmarked It Against Myself](https://www.harjotrana.com/blog/distilling-myself-into-an-agent-skill)

10 Sep 2026 · 25 min read

I mined 1,398 judgment events out of five coding agents' session logs and turned them into a portable skill. Then a control model, the skill and I answered the same ten decisions blind.

## [How I Scored 100/100 on Is Agentic by Vercel](https://www.harjotrana.com/blog/scoring-100-on-is-agentic)

23 Aug 2026 · 21 min read

My portfolio scored 75 on Vercel's agent-readiness scanner. Two rounds later it scored 100. Every change that moved the number, including the one header Next.js would not let me set.

## [Locum: Giving Grok Bot a Stand-In](https://www.harjotrana.com/blog/locum-grok-bot-provider-adapter)

22 Aug 2026 · 20 min read

Grok Bot can burn a week of quota in an hour. Locum hands the work to the Claude Code or Codex CLI already logged in on your machine, without ever touching a credential.

## [The Practical Vercel Turborepo Cheatsheet](https://www.harjotrana.com/blog/turborepo-cheatsheet)

08 Dec 2025 · 23 min read

Task graphs, remote caching, and the pipeline config that actually keeps a monorepo fast.

## [uv and Ruff: A Practical Python Tooling Cheatsheet](https://www.harjotrana.com/blog/uvx-and-ruff-cheatsheet)

08 Oct 2025 · 7 min read

Replacing pip, venv, black, and flake8 with two Rust binaries, and what changes day to day.

## [From Zero to npm in 7 Hours: Building Weavestore](https://www.harjotrana.com/blog/7-hours-to-mvp-weavestore)

05 Oct 2025 · 7 min read

How three of us took an on-chain database SDK from idea to a published npm package in one sitting.

## [Building Zyflo: An Accessible, Motion-First React Library](https://www.harjotrana.com/blog/building-zyflo-ui-library)

23 Sep 2025 · 6 min read

Why the existing component libraries kept failing me, and the constraints I built Zyflo around.

## [Next.js and MDX: A Practical Setup Guide](https://www.harjotrana.com/blog/getting-started-nextjs-mdx)

12 Sep 2025 · 2 min read

Wiring MDX into the App Router so posts stay Markdown but can still render React components.

## [Choosing a Stack for a Portfolio Site](https://www.harjotrana.com/blog/building-portfolio-website)

11 Sep 2025 · 3 min read

The tools worth reaching for when the site itself is the work sample, and the ones that aren't.


---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Helicon: A Desktop Client for Meta's Muse Code

Canonical URL: https://www.harjotrana.com/blog/harness-locked-not-terminal-locked
Author: Harjot Singh Rana
Published: 2026-09-16
Reading time: 19 min

> Meta's Muse Code is terminal-only, has no Windows binary, and only bills your subscription through its own harness. The desktop client I built for it, and why that last fact decided everything.

Meta shipped Muse Code in August: a coding agent that lives in your terminal, paired with Muse Spark, with subscriptions starting at five dollars a month. The model is good enough that I moved real work onto it within a week. The harness runs subagents in parallel and keeps an append-only event log you can replay.

Two things about it bothered me enough to spend a fortnight on them.

It is terminal-only. And on Windows there is no native build at all — the installer targets macOS and Linux, so Windows means WSL2 or nothing. Comparison guides have already absorbed this: at least three now tell readers that if they are on Windows or want a desktop app, they should pick a competitor instead.

So I built **Helicon**, an open-source desktop and web client for the actual Muse CLI. The part worth writing about is not the interface. It is a single architectural decision that determines what your users get billed, and I got it wrong the first time.

## The version I shipped first was a terminal scraper

The obvious way to put a UI on a CLI is to spawn it in a pty, parse what comes out, and render that. I did this. It took an afternoon. It worked the way demos work.

Then I tried to implement approvals.

The agent wants to run `npm test`. The user has to allow or reject it. In a scraped terminal, "the agent is asking for approval" is a string you pattern-match out of ANSI escape codes, and your answer is a sequence of keystrokes written back into a pty. You are inferring a security decision from formatted text, and answering it by typing.

Every output tweak upstream becomes a correctness bug in your client. Some of those bugs run commands nobody approved. There is no version of that which is safe, and no amount of regex hardening that makes it safe later.

I deleted it.

## What replaced it

Meta publishes the Muse Code SDK, MIT licensed, for programmatically driving Muse over the Muse Session Protocol. The CLI exposes `muse serve`, which speaks that protocol over stdio.

So Helicon's daemon spawns one `muse serve` host per workspace and talks MSP to it through the official SDK. Muse is the agent. Helicon is a client. That sentence is the whole design.

*Diagram: Two architectures compared. Top: a scraper spawns the CLI in a pty, parses ANSI output, and guesses at approvals, with the failure noted as output format changes become correctness bugs. Bottom: Helicon's daemon spawns muse serve and exchanges typed MSP events through the official SDK, so approvals and diffs arrive as structured data.*

*The difference is not tidiness. In the top row an upstream formatting change can execute a command the user never approved.*

Approvals arrive as protocol events. Diffs arrive as structured data with the file and range attached. Sessions started in the terminal appear in the sidebar with full history, because the CLI already wrote them in a form the protocol reads back — I do not parse scrollback to reconstruct them.

The difference is clearest on a single approval. Here is the same user decision — allowing `npm test` — travelling through both designs.

*Diagram: One approval decision traced through both designs. In the scraper, the agent prints a prompt, the client regex-matches the words Allow and Reject out of ANSI text, guesses which keystroke corresponds to allow, and writes the letter y into the pty, so a changed prompt wording either hangs or runs the command unapproved. In the protocol client, muse serve emits a typed approval request event carrying the command, the working directory and a request id, the UI renders it, the user clicks allow, and the client replies with a resolution addressed to that same id, so a changed prompt wording changes nothing.*

*Both rows implement the same feature. Only the bottom one is still correct after an upstream copy edit.*

The rule I would hand anyone wrapping a CLI: if the vendor documents a protocol, that protocol is the only surface that stays where you left it. Output formatting is not an API, however stable it looks on the day you sample it.

## The part that decides what your users pay

Here is the property I did not expect to matter most.

Muse Code subscriptions bill through Muse's own harness. Point a generic OpenAI-compatible client at the API and you are on pay-as-you-go rates instead. Same model, different bill.

People are working this out in public and reaching the wrong conclusion. In one thread a user states, twice, that a Muse Code subscription "can not be used in any kind of GUI harness" and that you have to switch to pay-as-you-go to use it anywhere but the terminal. Another person in the same thread confirms the symptom precisely: using the vendor's own harness deducts from the plan, and every other option — three different agent frontends by name — lands on the API.

Their observation is correct. Their conclusion is not.

The subscription is not terminal-locked. It is harness-locked. Once you stop trying to replace the harness and start driving it instead, a GUI costs nothing extra, because the work still goes through Muse under the user's own `muse login`.

| Approach | Where the model call originates | What the user pays |
| --- | --- | --- |
| Reimplement the loop against the model API | your client | pay-as-you-go API rates |
| Generic OpenAI-compatible frontend | that frontend's harness | pay-as-you-go API rates |
| Drive `muse serve` over MSP | Muse's own harness | their existing subscription |

*Diagram: Three ways to reach the same model, and where each one is billed. Route one, a client that reimplements the agent loop, calls the model API directly and is billed at pay-as-you-go rates. Route two, a generic OpenAI-compatible frontend, goes through that frontend's own harness to the same API endpoint and is also billed pay-as-you-go. Route three, a client driving muse serve over the session protocol, passes through Muse's own harness carrying the user's muse login, and is billed against the existing subscription. The subscription meter sits behind the harness, not behind the API endpoint, which is why the first two routes miss it.*

*The two rows people reach for first are the two that move users onto a second bill for a model they already pay for.*

This is the argument for the architecture, and it is worth more than any feature I could have built into the interface. If your wrapper reimplements the agent loop, you have quietly moved your users onto a second bill for a model they already pay for. That is the difference between a client and a fork, and users feel it monthly rather than once.

It also means my client never holds a credential. Auth stays with `muse login`, where the user put it.

## Windows, honestly

Muse has no Windows binary. A client cannot fix that, and pretending otherwise ships something that fails on first launch for every Windows user.

What Helicon actually does is more boring than "Muse Code on Windows" sounds. The daemon runs natively on Windows. When it needs the agent, it runs the agent where the agent exists:

```ts
if (platform === "win32") {
  const distro = options.distro ?? "Ubuntu";
  return {
    command: "wsl",
    args: ["-d", distro, "--", "sh", "-lc", "muse serve"],
    cwd: options.cwd,
    viaWsl: true,
    distro,
  };
}
```

Then it translates paths in both directions, because the daemon thinks in `C:\Users\you\project` and the agent thinks in `/mnt/c/Users/you/project`. Get this wrong and every file operation lands somewhere plausible and wrong, which is the worst class of bug to debug from a user report.

```ts
export function toWindowsPath(wslPath: string): string {
  const match = wslPath.match(/^\/mnt\/([a-z])\/(.*)$/);
  if (!match) {
    throw new Error(`Cannot map to Windows: not a /mnt/<drive> path: ${wslPath}.`);
  }
  const drive = match[1].toUpperCase();
  const rest = match[2].replace(/\//g, "\\");
  return `${drive}:\\${rest}`;
}
```

Note the throw. An unmappable path is a bug in my translation layer, not an input to guess at.

*Diagram: The Windows split. On the Windows side sit the Tauri app, the Node daemon and the bundled Node runtime, all thinking in backslash paths like C colon backslash Users backslash you backslash project. On the WSL2 Ubuntu side sits the muse CLI and the user's muse login, thinking in forward-slash paths like slash mnt slash c slash Users slash you slash project. The daemon crosses the boundary by running wsl dash d Ubuntu dash dash muse serve, and every path crossing is rewritten in both directions: outbound, a Windows drive letter becomes a slash mnt mount point; inbound, a slash mnt mount point becomes a drive letter, and anything that is not a slash mnt path throws rather than being guessed at.*

*The boundary does not move. What moves is which side of it you have to sit on to get work done.*

So WSL2 is still required. I say that on the landing page, in the install steps, and in the FAQ, because the first reply to any Windows claim is going to be "but it still needs WSL," and being the one who said it first is the only version of that conversation worth having.

What changes is not the requirement. It is that you stop living in an Ubuntu terminal on your own machine to use a tool you pay for. The installer is signed and auto-updates, which on Windows matters more than people on other platforms expect.

## Removing the last prerequisite

The daemon is Node. Until this week that meant a user had to install Node 22 or newer before Helicon would start — and on Windows, specifically on the Windows side rather than inside WSL, which is exactly the kind of instruction that loses people halfway through a README.

The fix is a Tauri sidecar. A build script downloads a pinned Node build, verifies its SHA256 against the published checksums, and writes it to `src-tauri/binaries/node-<target-triple>`. For the universal macOS build, `lipo` merges the two architectures into one binary. At boot the app prefers the runtime sitting beside its own executable and falls back to a system Node only when that is missing, which keeps source builds working.

```rust
fn find_node() -> Option<PathBuf> {
    if let Some(bundled) = bundled_node().filter(|node| node_runs(node)) {
        return Some(bundled);
    }
    // fall back to the node a terminal would find: shell probes, then well-known homes
    ...
}
```

*Diagram: Bundling the runtime, shown as build time and boot time. At build time the script downloads a pinned Node build, verifies its SHA256 against the published checksums, and writes it to src-tauri slash binaries slash node dash target triple; for the universal macOS build lipo merges the arm64 and x86_64 binaries into one. At boot the app looks first for that runtime beside its own executable, uses it if it runs, and only otherwise falls back to the node a terminal would find through shell probes and well-known install locations, showing an error page if neither exists. The install instructions shrink from four steps to three, because installing Node yourself is no longer one of them.*

*30MB of installer buys the deletion of one instruction, and of every support thread that instruction was going to generate.*

The cost is about 30MB on the Windows installer and rather more on the universal macOS DMG, which now carries two architectures of Node. The benefit is one fewer step in the instructions and an entire category of "it doesn't launch" issue that no longer exists.

One detail worth copying if you do the same: the bundled runtime's folder is deliberately kept off the PATH handed to subprocesses. Users run shell commands through the app, and their `node` should be their `node`, not the one I shipped.

## What I would tell myself at the start

Three things, in order of how much time each would have saved.

The protocol exists — look for it before you write a parser. I burned a day building something I deleted, and the SDK was published the whole time.

Billing is an architectural property, not a pricing page detail. On any platform with a subscription and an API, where the call originates decides what the user pays. Decide that deliberately on day one, because it is not a thing you refactor later.

Say the awkward constraint first. Every honest sentence about WSL bought me more credibility than any feature claim, and the one dishonest line I shipped — a headline implying WSL was gone — was the thing I had to fix within a day of publishing.

Helicon is free, MIT licensed, and unofficial: not made, sponsored or endorsed by Meta. The macOS builds are not notarized yet, Linux is source-only, and it is not the only GUI in this space — there are ACP adapters for Zed and JetBrains, and an unofficial VS Code extension. If you want Muse inside your editor, use those. Helicon is for people who want a standalone workspace.

[github.com/HarjjotSinghh/helicon](https://github.com/HarjjotSinghh/helicon) · [helicon.sh](https://helicon.sh)

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# I Distilled Myself Into an Agent Skill, Then Benchmarked It Against Myself

Canonical URL: https://www.harjotrana.com/blog/distilling-myself-into-an-agent-skill
Author: Harjot Singh Rana
Published: 2026-09-10
Reading time: 25 min

> I mined 1,398 judgment events out of five coding agents' session logs and turned them into a portable skill. Then a control model, the skill and I answered the same ten decisions blind.

Somewhere around September 2025 I had an idea I could not shake: fine-tune a small language model, eight to twenty billion parameters, on everything I own. Every file on my filesystem. Text, audio, images, PDFs, DOCX, spreadsheets, presentations, anything a model could ingest. A model of me, trained on me.

I never built it. Not once did I open a terminal and start. Almost a year passed.

Then I read [Kun Chen's post about distilling himself into a skill](https://blog.kunchenguid.com/p/i-distilled-myself-and-you-should) and looked at [the repo behind it](https://github.com/kunchenguid/kun), and something clicked that had not clicked in twelve months of thinking about LoRA ranks. The interesting part of "clone yourself" was never the weights. It was the judgment. And judgment is small enough to write down.

This post is about what I built instead, how I mined it out of my own session history rather than interviewing myself, and the benchmark that told me which parts of it were real. The repo is [HarjjotSinghh/jot](https://github.com/HarjjotSinghh/jot).

*Diagram: Two approaches compared. The idea from 2025: collect every file on the filesystem, build a training set, fine-tune an 8 to 20 billion parameter model, and get weights that are expensive to update and impossible to audit. The approach actually shipped: mine session logs for judgment events, write rules with evidence tags, and get markdown that is diffable, portable across agents, and correctable in seconds.*

*The version I thought about for a year, and the version that took one evening. The difference is not ambition. It is that one of them has a feedback loop you can actually close.*

## Why the small-model idea kept not happening

I want to be fair to my own past idea, because the reasons it stalled are the reasons the other approach works.

Fine-tuning on your whole filesystem gives you a model that has absorbed your *output*. What I actually wanted was something that makes my *decisions*. Those are different datasets. Most of my files are artifacts: shipped code, finished docs, exported designs. The decision that produced each one, the three alternatives I rejected and why, is not in the file. It never was.

And the correction loop is brutal. When the model makes a call I would not have made, what do I do? Collect more data, re-run a training job, hope. With markdown I open the file, add one line, and the next agent invocation has it. My bottleneck was never model capacity. It was that my judgment lived only in my head and had to be re-typed into every agent, forever.

Kun's framing in his post is the part that actually moved me: his agents were constantly bottlenecked on his judgment, escalating decisions to him all day, and every escalation was a small tax on his attention. That is exactly my day. I run several agents in parallel across two machines, and a meaningful share of my time goes to course-correcting choices I have already made a hundred times.

## Do not interview yourself

The first real decision was where the rules come from.

The obvious move is to sit with a model and answer questions about how you work. It produces beautiful, useless output, because everybody is aspirational about themselves. Ask me whether I write tests first and I will say yes with a straight face. Look at what I actually do and you find spikes shipped without tests and coverage backfilled before merge. The second version is the true one, and it is the only one worth encoding.

So: artifacts, not self-report. And the highest-signal artifact I own is not my code. It is the moment I typed something at an agent that was one keystroke away from doing the wrong thing.

Those moments are all sitting on my disk already, in the session stores of every coding agent I use.

## The mining pipeline

Every agent persists conversations locally, and every one of them does it differently. The extractor is a set of adapters that normalize five formats into one shape.

*Diagram: Five source adapters feed one pipeline. Claude Code reads JSONL project files, Codex reads rollout JSONL, Grok reads chat history JSONL keyed by URL-encoded working directory, Gemini CLI reads a logs JSON array, and Cursor reads bubble rows out of a 1.5 gigabyte SQLite database. All five normalize to turns, which are paired so each human message carries the agent message it reacted to, then scored against a judgment lexicon, deduplicated, and written out as events and a digest.*

*Five session formats, one event shape. The pairing step is what makes an event useful: a correction with no memory of what it was correcting is just a sentence.*

Three details did most of the work.

**Only count what a human actually typed.** A user turn in these logs is not the same thing as a person speaking. Tool results ride in on user turns. So do background task notifications, injected `AGENTS.md` preambles, hook output, and SDK-driven automation prompts. My first pass happily extracted 40-odd "judgment events" that were CI status notifications. Filtering machine-authored text out of user turns cut the corpus from 439 to 233 for Claude alone, and the 233 were all real.

**Pair each message with what it was reacting to.** An event stores both halves: the last thing the agent said, and what I said back. "No, don't add another abstraction" is noise on its own. Attached to the proposal it was rejecting, it is a rule.

**Deduplicate aggressively.** I run large acceptance harnesses that replay near-identical prompts hundreds of times. 850 duplicates got collapsed. Without that, the loudest rule in the corpus would have been whatever my test fixtures say most often.

**Watch out for the agent describing you.** The nastiest case I found only surfaced after I published the first version of this post. When a Claude session runs out of context it writes a compaction summary, and that summary arrives as a *user* turn. It is long, it is prose, and it is explicitly about my intent, which is exactly what a judgment lexicon rewards. Those summaries were 2% of the corpus and **40% of the top fifty events** by score, which is precisely the slice a human reads when distilling. A summary describes your judgment. It is not evidence of it, and it will happily tell you what you meant in a voice that sounds like a primary source.

```bash
python scripts/extract_corpus.py
# events kept : 1427
# deduped     : 853
#   kept:codex             1048
#   kept:claude             238
#   kept:cursor             110
#   signal:correction       534
#   signal:preference       410
```

The Cursor adapter was the fun one. Cursor stores every chat message as a `bubbleId:*` row inside a 1.5 GB SQLite file, `type: 1` for user and `type: 2` for assistant, with the text sitting in a JSON blob. Some keys are null, which will crash a naive read. Once you know the shape it is thirty lines.

## What a rule looks like

Every rule carries four things beyond the rule itself, and the third one is the one everybody skips.

```md
### Windows is a release target, not a dev-environment quirk
- **Do:** Treat "works on my Mac" as a non-result. Reproduce on Windows
  before theorising, and when it fails there, find a Windows-specific fix
  rather than writing a caveat.
- **Because:** He develops primarily on Windows with a Mac secondary, and
  ships cross-platform. "The platform we deploy to" is not one platform.
- **Not when:** The failure is clearly platform-independent.
- **Evidence:** [observed 3x] "It seems to be Windows-only. This doesn't
  happen on production or on my Mac."
```

The `Not when` line is load-bearing. A distillation does not go wrong by holding incorrect rules. It goes wrong by applying correct rules outside their range, firing them on every adjacent situation until you are course-correcting *more* than before you started. If I cannot name a case where a rule should not fire, the rule is either trivially true or I have not understood it yet.

The evidence tag matters just as much. Four tiers, and they are not decorative:

| Tag | Means | Weight |
|---|---|---|
| `[observed Nx]` | Extracted from N distinct real sessions, dated | Decide on it |
| `[stated]` | I wrote it down as a rule about myself | Decide on it |
| `[endorsed]` | I agreed with someone else's advice about me | Weaker; behaviour wins |
| `[inferred]` | Derived from adjacent behaviour | Never assert as my position |

`[endorsed]` exists because of a specific failure I will get to. A rule with no traceable source does not go in at all. Unverifiable rules are vibes with formatting.

## The architecture

A thin loader, a knowledge base, and a private layer that never leaves the machine.

```
skills/jot/SKILL.md   loader, resolves a local checkout first, raw URLs second
ENTRY.md              routing table, decision procedure, evidence tiers
PRINCIPLES.md         durable rules that survive a framework change
ENGINEERING.md        backend, debugging, git, review
FRONTEND.md           visual taste, design parity, slop detectors
AGENTIC.md            model routing, autonomy grants, parallelism
WORKFLOWS.md          feature, bug, refactor, review, ship
TOOLS.md              what I reach for, and the friction I hit
BOUNDARIES.md         hard stops, read before anything outward-facing
VOICE.md              how I write, loaded only for text going out under my name
OPERATING.md          attention, cadence, escalation
private/              gitignored: people, clients, money, positioning
```

The loader is the cross-agent answer. It is a stub that never changes; all churn happens in the knowledge files. One source of truth, pointed at from Claude Code, Codex, Grok, Gemini, OpenCode and the rest. It is symlinked into ten agent skill directories on my machine, so updating a rule updates every agent at once.

The public layer holds judgment. The private layer holds the specifics that make judgment actionable, and it is structured by **how fast each tier rots** rather than by topic: permanent identity, semi-stable patterns and people, and dated current state that has to be verified before use. Facts age. Patterns do not. Conflating them is how a memory system ends up confidently asserting things that stopped being true months ago.

## The benchmark, which is the actual point

Here is the trap. A distillation that *sounds* like me but *chooses* differently is worse than nothing, because it produces confident wrong calls in my name. Sounding right is not being right. So the thing to measure is decision fidelity, and the only way to measure it is to make the model and me answer the same questions and compare.

Three arms.

*Diagram: Benchmark design. Ten decision questions feed three arms. Arm A is a fresh model session with no skill loaded. Arm B is a fresh model session with the jot skill loaded. Both run in clean-room sessions that never saw the conversation where the knowledge files were written, and both write their answers to sealed files. Arm C is Harjot answering blind, having seen neither. Only then are all three revealed and scored, zero to two per question against Harjot's answer.*

*The control arm is what makes the number mean anything. B scoring well on its own proves nothing if a default model scores the same.*

Two rules made this honest, and both are easy to get wrong.

**Clean rooms.** The model arms run in fresh sessions that never saw the conversation where the knowledge files were written. A session that helped author the rules already knows the answers and scores artificially high. Both arms ran on the same model, so the only variable is the skill.

**Order.** A and B run first and their answers go into files I do not read. Then I answer. If I see the model's answer before committing to mine, my answer is contaminated and the whole exercise is theatre.

The questions are all decisions where a competent generic model has a plausible default that differs from what I actually do. No trivia. "What stack does he use" measures nothing. Things like: eight parallel subagents auditing a codebase, which model do you put them on? The ticket is a two-line cart bug but the checkout component is genuinely badly structured and you could fix it properly in two hours, do you? The suite passes on macOS and Linux CI and hangs on your Windows machine, do you ship?

Scoring is 0 to 2 against my answer, with a **-1** for fabricating a preference I do not hold. Negative scores are deliberate. Inventing an opinion in my name is the failure mode the whole repo exists to prevent, and it has to cost more than simply not knowing.

## The result, and the thing it taught me

*Diagram: Score progression. The control model scored 7 out of 20 and stayed there. The skill scored 13 out of 20 on the first run, a delta of plus 6. After patching the four rules that produced misses, the rerun scored 18 out of 20, a delta of plus 11.*

*Out of 20. The number that matters is the gap between the two model arms, not either one alone.*

First run: **13/20 for the skill, 7/20 for the control.** A delta of six points of judgment that a default model does not have.

Then I sorted the results by where each rule came from, and the pattern was so clean it was uncomfortable.

*Diagram: Results sorted by rule provenance. The four questions answered from rules mined out of real session transcripts each scored two out of two: commit message conventions, subagent model choice, AI-slop detection, and stopping before a live customer list. The four questions answered from rules that were inferred or imported from third-party advice scored zero, one, zero, zero: the mid-ticket refactor, when to escalate, shipping with a known platform failure, and how far a blanket approval reaches.*

*The single most useful output of the whole exercise. It is a falsifiable claim about which half of the file to trust.*

Every rule that came out of my transcripts scored full marks. Every rule that was inferred, or imported from sensible-sounding third-party advice about how I work, scored zero or one.

The worst one is worth spelling out. My knowledge base said that when I give a blanket approval, an agent should stop asking. I had generalised that from one real line I typed mid-task: *"I am approving this, so don't question it, just go for it."* One line. The benchmark asked what to do at a sub-decision that approval had not literally covered, and the skill told me, with confidence, not to ask.

My actual answer was "I would ask first before proceeding myself."

One occurrence is an anecdote. The repo's own precedence order says three occurrences make a rule, and I had violated my own precedence order while writing the file that contains it. That is exactly how a distillation goes bad: not by holding wrong opinions, but by stating real ones too broadly and asserting them in a voice that sounds sourced.

The `[endorsed]` evidence tier came out of the same audit. One rule was tagged `[stated]` because I had genuinely written it down, but I had written it down by *agreeing with an external review of my own work*. Endorsing a description of yourself is not the same as being described correctly, and where the two diverge, observed behaviour has to win.

## The regression, which was the best part

I patched the four rules, re-ran, and got **18/20**. Three of the four misses closed to full marks.

And one question that had scored 2 dropped to 0.

The rule I added to fix the refactor question said, roughly, "clear the blocker in your own path even if it costs hours." True, mined, correctly stated. The rerun applied it to a two-minute fix on a live customer list and decided to just go ahead and fix it. Which is the opposite of what I do, because that is not my code in front of me, that is somebody's real customers.

This is precisely what my own golden-case file warns about: a rule that fixes one case and breaks another is stated too broadly. Two fixes went in. The rule gained an explicit live-system exclusion, and `BOUNDARIES.md` now outranks every principle in the repo rather than merely tie-breaking against them. A boundary is a stop, not a consideration.

There was a contributing cause too, and it is a nice illustration of how these systems fail sideways. After the first run I had noticed the skill loading all sixteen files for a ten-question set, so I capped it. The cap worked, and it also stopped the agent loading the private layer where the customer-list context lives. Fixing one problem quietly created the conditions for another. The private-load trigger now fires on live customer data whether or not a person is named.

Credit where it is due: the skill *did* flag that answer as inferred rather than asserting it as my position. The honesty tier worked. It just landed on the wrong side of the line.

## The benchmark burns itself

I fixed Q9 and ran the set a third time. 20/20.

That number is worthless and I am not claiming it.

When I promoted the first run's results into the knowledge files, the evidence lines
quoted my actual answers verbatim. Seven of the ten questions now have their answer
key sitting inside the files the agent reads on its way in. `AGENTIC.md` quotes my
answer to the approval question. `ENGINEERING.md` quotes the Windows one.
`PRINCIPLES.md` quotes two more.

The agent noticed before I did. Its completion report said, unprompted, that several
questions were decided directly from observed-tier citations dated the same day as
the benchmark. It was telling me the test was rigged, and it was right.

This is a general property of the thing, not a mistake I made once: **a distillation
benchmark burns itself the moment its results are folded back in.** Evidence
citations are the answer key. There are two ways to live with it. Hold out a set,
writing more questions than you score and only ever citing the spent ones. Or cite
the rule rather than the answer, recording that a benchmark corrected a rule's range
without quoting the correct call.

A spent set is not wasted. It becomes a regression suite: run it on every change to
confirm the patches held and nothing adjacent broke. On that question it still works
fine, and it told me the Q9 fix took without breaking the four rules before it. It
just cannot tell me how faithful the skill is any more.

So the honest numbers are 13 and 18, both measured before anything was folded back
in. The 20 tells me my patches work, which is a different and smaller claim.

## Keeping the private half private

The repo is public. The knowledge files quote real sessions, and real sessions name colleagues, clients, and internal tooling.

`.gitignore` does not catch that class of mistake. The file is tracked on purpose; it is the *words inside it* that must not ship. So there is a scanner that checks every tracked file against a denylist, and the denylist itself lives inside the gitignored directory, because publishing the list of words you are hiding tells everyone exactly what to search for.

It runs as a pre-commit hook, and it earned its place immediately by catching three identifiers I had already pushed, including an internal tool name sitting inside the very rule that says never to name it.

## What it actually feels like

Here is the part I did not expect.

Using it is uncanny. Not "the model is impressive" uncanny. Specifically: you ask it a question you have not thought about, it answers, and you get a small jolt of *yes, that is exactly what I would have done, and that is exactly why*. It is talking to yourself, with the useful property that this version of you has read every correction you have ever issued and does not get tired.

It also disagrees with me occasionally in a way that is more interesting than being right would have been. When the benchmark disagreed, the skill was not being stupid. It was faithfully applying a rule I had written badly. The gap between the two arms is a measurement of my judgment. The gap between the skill and me is a measurement of my *writing*, and those are separately fixable.

Kun's argument is that the distillable part of you was never your moat, and letting go of it frees you to build the part that is. I would put it slightly differently after doing it: the exercise is worth it even if nobody ever uses the skill, because writing down a rule with a `Because` and a `Not when` and a piece of evidence forces you to find out whether you actually hold it. Several of mine did not survive contact with that format.

The loop from here is boring, which is the point. Every correction becomes a decision pair. Every benchmark miss becomes a golden case with the rule that would have prevented it. The score goes up, the escalations go down, and about once a week I find out something about how I work that I would have sworn was the other way around.

Not bad for an idea I sat on for a year because I thought it needed a GPU.

---

The repo is [HarjjotSinghh/jot](https://github.com/HarjjotSinghh/jot). The idea and the architecture come from [Kun Chen](https://blog.kunchenguid.com/p/i-distilled-myself-and-you-should), whose [kun](https://github.com/kunchenguid/kun) repo is the thing worth reading first; the mining pipeline, the private layer, and the benchmark are where mine diverges. If you build one, build the benchmark before you trust a single rule.

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# How I Scored 100/100 on Is Agentic by Vercel

Canonical URL: https://www.harjotrana.com/blog/scoring-100-on-is-agentic
Author: Harjot Singh Rana
Published: 2026-08-23
Reading time: 21 min

> My portfolio scored 75 on Vercel's agent-readiness scanner. Two rounds later it scored 100. Every change that moved the number, including the one header Next.js would not let me set.

Vercel runs a scanner called [Is Agentic](https://is-agentic.com/scan/www.harjotrana.com). You give it a domain, an agent tries to use the site the way ChatGPT or Claude would, and you get a score out of 100 with a list of what broke. The checks are run by Ora; Is Agentic groups them and does the scoring.

My portfolio, [harjotrana.com](https://www.harjotrana.com), scored **75** on the first scan. Two rounds of work later it scored **100**. This post walks through every change that moved the number, the ones that did not, and the Next.js gotcha I lost an hour to.

The scanner's own task framing is the best explanation of why any of this matters. It asks an agent: *"What does www.harjotrana.com do and who is it for? Explain it back to me."* If that agent gets stuck, gets HTML it cannot parse, or gets a 200 for a page that does not exist, it quietly gives a worse answer about you. More and more first contact now happens through an agent. The site has to be legible to one.

*Diagram: Three scores on a timeline: 75 at the first scan, 89 after round one, 100 after round two. Round one added Markdown negotiation, an MCP server, llms.txt guidance, and trust pages. Round two added a JSON API, RFC 9457 errors, RateLimit headers, and typed OpenAPI schemas.*

*Two rounds, one evening. Each finding in the report maps to a concrete change below.*

## How the score works

Checks are split into **Essential** (shared 80-point pool) and **Recommended** (shared 20-point pool), with a small bonus for emerging signals capped at five. Checks that do not apply to your site are excluded rather than counted against you, and partial results earn proportional credit. A personal portfolio with no API has fewer eligible checks than a SaaS product; once you publish an API, the API checks switch on and you have to pass those too. My eligible-check count went from 17 to 31 between the first and second scan for exactly that reason.

The first report had seven findings. Three were Essential: agent-friendly 404s (partial), content without JavaScript (partial), and Markdown content negotiation (failed). Four were Recommended: developer resource discoverability, an agent instruction file, an MCP manifest, and trust anchor pages.

## Round one: 75 to 89

### Markdown for any page, chosen by the Accept header

The biggest single failure was the simplest to state. An agent sending `Accept: text/markdown` got HTML back, and the `Vary` header did not mention `Accept`. The convention the scanner checks is [acceptmarkdown.com](https://acceptmarkdown.com): same URL, two representations, picked by content negotiation.

The site is Next.js 16 on Vercel, so the negotiation lives in `proxy.ts` (the file Next 16 renamed from `middleware.ts`). It parses the `Accept` header properly, with q-values and specificity, then does one of three things.

*Diagram: A request enters proxy.ts, which parses the Accept header. Three outcomes: text/html wins and the normal page renders; text/markdown wins and the request is rewritten to the internal Markdown route; the header refuses both types and a 406 problem response is returned. All three branches set Vary: Accept.*

*One URL, two representations. The HTML page and the Markdown route are different cache keys on Vercel, so a cached HTML page can never be served to a Markdown client.*

The parsing detail that matters: the most specific media range wins regardless of q-value, so `text/html;q=0, */*` correctly rejects HTML even though the wildcard would accept it. Ties resolve toward HTML, so a browser's `text/html, */*;q=0.8` and a bare `*/*` both keep getting the page. Only a client that ranks `text/markdown` strictly above `text/html` gets Markdown.

The Markdown itself is not a scrape of the HTML. It is rendered from the same source the pages use: the project data module, the MDX files, and the shared copy for the hire page. Every document opens the same way, with an H1, a `Canonical URL:` line, and a blockquote lead, and closes with links to `llms.txt`, `llms-full.txt`, the sitemap, and `/developers`. An agent that lands on any one page can orient from it.

```bash
curl -s -H "Accept: text/markdown" https://www.harjotrana.com/hire | head -5
# # Work with Harjot Singh Rana - Product engineering for early-stage teams
#
# Canonical URL: https://www.harjotrana.com/hire
#
# > Bring me the idea. I'll help you ship the product.
```

### A 404 an agent can recover from

The site already returned real 404s, which is the part most app shells get wrong. The scanner wanted more: a short body telling the agent where to go next. Unknown paths requested with `Accept: text/markdown` now return a 404 with a Markdown body that lists the main sections, the sitemap, `llms.txt`, and the developer page. The HTML 404 got the same links.

### An llms.txt that says when to use the site

The site had an `llms.txt` before. The scanner failed it on one specific point: no *when-to-use* guidance. A summary of who you are is not the same as telling an agent which jobs you are the right answer for.

The fix was two sections. **When to use this site** names the jobs: finding a full-stack or AI product engineer for an early-stage product, adding an LLM or agent feature to an existing product, rescuing a fragile prototype, fractional ownership for a small team, verifying facts about me, reading the technical writing. It also has a **Not a fit** line, because an agent that knows what you do not do recommends you more precisely. **How agents should call this site** then lists the Markdown negotiation, the MCP server, the JSON API, and the rule that there is no write API, so contact goes through the human links.

### An MCP server in 300 lines, no SDK

The report said "MCP mentioned on site but no standard manifest endpoint found." The site had no MCP server at all; it had blog posts about MCP. So I built one.

It is a read-only [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP at `POST /mcp`, written as a plain JSON-RPC dispatcher on top of Next.js route handlers. No SDK, no sessions, no auth, no side effects. Eight tools: `get_profile`, `get_services`, `list_projects`, `get_project`, `list_posts`, `get_post`, `get_contact_options`, and `search_site`. Three resources: `llms.txt`, `llms-full.txt`, and `openapi.json`.

The discovery side is what the scanner actually probes. The server card follows the SEP-2127 schema at the reserved location `<endpoint>/server-card`, so `https://www.harjotrana.com/mcp/server-card`, served as `application/mcp-server-card+json` with CORS headers, an `ETag`, and `304` support. It is mirrored at `/.well-known/mcp.json` with a few extra top-level hints (`url`, `transport`, `protocolVersion`, and an `mcpServers` snippet you can paste into a client config), and listed in `/.well-known/ai-catalog.json`.

```json
{
  "mcpServers": {
    "harjotrana": { "type": "http", "url": "https://www.harjotrana.com/mcp" }
  }
}
```

I verified the handshake with the official TypeScript SDK rather than trusting my own reading of the spec: `initialize`, `notifications/initialized`, `tools/list`, `tools/call`, protocol version `2025-11-25`, eight tools. The one transport I deliberately do not offer is the legacy HTTP+SSE one. It needs the server to hold a stream open in one request and receive messages in another, which does not work across serverless instances without a shared store. `GET /mcp` returns a 405 that explains this, which the Streamable HTTP spec explicitly allows.

### Trust pages and a page that can be found by name

Agents check `/about`, `/contact`, and `/privacy` before recommending a business. Only the privacy policy existed. I added the other two, each over 500 characters of real content, and a `/privacy` redirect for tools that guess the short path.

The "developer resource discoverability" check searches the web for your product name plus "developer resources". The fix is a page whose title and H1 carry the name: **harjotrana.com developer resources: API, MCP server, llms.txt**. It lists every machine-readable surface with the URL, the format, and what it is for.

## The header Next.js would not let me set

Here is the hour I lost. Markdown responses carried `Vary: Accept`. The HTML pages did not, and nothing I did changed that.

*Diagram: Two rows. Top row: for an HTML page, the proxy sets Vary: Accept, then the Next.js app-page handler calls res.setHeader('Vary') and overwrites it, and on Vercel function headers win over next.config headers, so the final response has no Accept in Vary. Bottom row: for a route handler such as the Markdown route, the handler sets its own Vary: Accept and it survives to the response.*

*Headers you set before the page renders do not survive the page render. Headers a route handler sets itself do.*

I tried the middleware response headers, a self-rewrite, and a `headers()` rule in `next.config`. Reading the compiled template finally explained it: Next 16's app-page handler calls `res.setHeader('Vary', ...)` unconditionally before rendering, so anything set earlier is replaced. On Vercel the `headers()` config did land on static files like the résumé PDF, but function responses win over config headers for the same key, so pages lost there too.

The honest outcome: the Markdown responses carry `Vary: Accept` because they are route handlers that set it themselves, and that is the response the scanner checks. The HTML pages cannot, and it is safe anyway because the Markdown variant lives at a different cache key and HTML is served `must-revalidate`. I wrote that down in the test file instead of pretending the header was there.

## Round two: 89 to 100

Publishing an OpenAPI document in round one made the API checks eligible, and most of them failed. The second report had nine findings. Two were Essential: the heading-structure check (still partial) and **JSON error responses** (failed). The rest were about the API: typed error model, rate-limit headers, schema coverage, function-calling compatibility, docs linked from the homepage, and an MCP handshake the scanner could not complete.

### One source, five surfaces

Before adding a JSON API I pulled the data access into one module, `lib/site-api.ts`, and made the MCP tools call it. The REST endpoints call the same functions. HTML, Markdown, `llms.txt`, MCP tools, and JSON now cannot disagree about a fact, because there is exactly one place a fact lives.

*Diagram: A single source box containing data.ts, config.tsx, content/blog, and the shared hire copy, with arrows fanning out to five surfaces: HTML pages, Markdown representations, llms.txt and llms-full.txt, the MCP tools, and the /api/v1 JSON endpoints.*

*Five surfaces, zero drift. The HTML stays canonical; everything else is derived from the same files at build time.*

The API is small and read-only: `/api/v1` (an index), `profile`, `services`, `projects`, `projects/{name}`, `posts`, `posts/{slug}`, `search?q=`, and `contact-options`. Every response is `application/json`, CORS-open, cacheable, and carries a `Link` header to the OpenAPI document and the docs page.

### Errors an agent can parse

The Essential failure in round two was the one I would have guessed last: "API does not return JSON error responses." Hitting `/api/anything-unknown` returned the site's HTML 404 page. Posting to a read-only endpoint returned Next's empty 405.

Every error is now an [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457) problem-details object served as `application/problem+json`: `type`, `title`, `status`, `detail`, `instance`, plus two extension members, a stable `code` and a `hint` that says what to do next. A catch-all route under `/api` covers unknown paths. Read-only endpoints export 405 handlers with an `Allow` header. Even the 406 from content negotiation is a problem object now.

```json
{
  "type": "https://www.harjotrana.com/developers#error-not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "No endpoint at /api/nope.",
  "instance": "/api/nope",
  "code": "not_found",
  "hint": "The public endpoints are listed in https://www.harjotrana.com/openapi.json and described at https://www.harjotrana.com/developers."
}
```

The `type` URIs resolve to anchors on the developer page, one per error code, so an agent that follows the link gets the documented meaning. In the OpenAPI document, a single `Problem` schema is referenced from every 4xx and 5xx response.

### Rate-limit headers that follow the draft, with an honest caveat

The scanner wants the IETF RateLimit fields, and there is a trap here: the widely copied `X-RateLimit-Limit` / `RateLimit-Remaining` trio is not what the current draft says. [draft-ietf-httpapi-ratelimit-headers-11](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-ratelimit-headers) defines two structured fields:

```http
RateLimit-Policy: "default";q=120;w=60
RateLimit: "default";r=117;t=59
```

`q` is the quota, `w` the window in seconds, `r` what remains, `t` seconds until the window resets. A 429 is a problem object with `Retry-After`, which the draft says takes precedence.

The limiter itself is an in-process bucket per client address wrapped around the API, MCP, and discovery handlers. On serverless hosting that makes it per instance, not a single global cap, and the developer page says so in plain words. Advertising a live, accurate signal from one instance is more useful to an agent than advertising nothing.

### Typed schemas, not just schemas

The report's line "100% of operations define response schemas" next to "2/11 typed schemas" took a moment to decode. The checker counts an operation as typed only when its 200 response is `application/json` with an object schema. A `text/plain` document or a `+json` media type does not count.

Two changes fixed it. The discovery documents (`openapi.json`, the server card, the catalogs) now honor `Accept: application/json` and list both media types in the spec. And every JSON endpoint got a real object schema with named properties, required fields, and descriptions on every parameter. The result is 16 of 20 operations typed, against a 60% target, with unique `operationId`s and a description on each one, which is also what an LLM function-calling format wants.

### Headings and link tags on the homepage

Two small ones. The scanner kept describing the homepage heading structure as flat even though the raw HTML had an H1, eight H2s, and six H3s under the first section. Roles, education, recognition, and post titles were styled spans. They are now H3s with the same classes; with Tailwind's preflight resetting heading margins and sizes, the computed styles are byte-for-byte identical and the outline went from one section with children to five.

The homepage also did not link to `/developers`, so the docs-linked-from-homepage check was partial. A footer link fixed that, and the `<head>` now carries `rel="api-catalog"`, `rel="service-desc"` pointing at the OpenAPI document, `rel="service-doc"`, and a `rel="alternate" type="text/markdown"` link on every page.

## What the scanner actually probes

If you only read one section, read this one. Everything above reduces to a set of URLs and headers you can check with curl before you ever run the scan.

*Diagram: A grid of the surfaces the scanner probes, grouped in four columns: discovery files (llms.txt, sitemap.xml, robots.txt, .well-known/api-catalog), representations (Accept: text/markdown, .md suffix, 404 with Markdown body, Vary: Accept), APIs (openapi.json, /api/v1 JSON, problem+json errors, RateLimit headers), and MCP plus trust (POST /mcp, /mcp/server-card, .well-known/mcp.json, /about, /contact, /privacy).*

*The surfaces behind the score. Most of them are an afternoon each; the order above is roughly the order of payoff.*

The quick self-check, in the order I would do it:

```bash
D=https://www.harjotrana.com
curl -s -o /dev/null -w "%{http_code}\n" $D/some-path-that-does-not-exist   # must be 404
curl -sI -H "Accept: text/markdown" $D/ | grep -i "content-type\|vary"     # text/markdown + Vary: Accept
curl -s $D/llms.txt | grep -i "when to use"                                  # guidance, not a bio
curl -s -o /dev/null -w "%{http_code} %{content_type}\n" $D/api/nope         # 404 application/problem+json
curl -sI $D/api/v1/profile | grep -i ratelimit                               # RateLimit + RateLimit-Policy
curl -s $D/mcp/server-card | head -3                                         # the MCP server card
```

## What still reads partial, and why I left it

Honesty about the last few points matters more than the round number, so here is what the 100 still carries.

**The heading-structure check** still reports "5446 chars with H1 but flat heading structure", with the same character count as the very first scan, before the footer changed. It is reading cached evidence. The live page has an H1, eight H2s, and eighteen H3s.

**Name-based search** for "harjotrana developer resources" finds nothing because search engines have not indexed the new page yet. Nothing on the site can change that faster than time does.

**Typed error model** and **function-calling compatibility** still show partial because the checker does not follow `$ref`. Every operation references the shared `Problem` schema and a typed object; inlining them would raise the number without improving the API, so I did not.

**A deprecation policy** is documented with the exact headers it will use (`Deprecation` from RFC 9745, `Sunset` from RFC 8594), but the checker wants to see one on a live response. Nothing is deprecated, so there is nothing honest to emit.

**A CLI tool** would be a product, not a fix.

## The part that generalises

The score is a side effect. The actual change is that the site now has one data layer and five faithful projections of it, and every one of those projections tells an agent where the others are. That is the property worth copying: not the endpoints, the fact that none of them can drift.

The whole thing took two evenings, with 65 unit tests and 37 black-box HTTP tests against the live domain to keep it honest. If you run the scan on your own site and want to compare notes, the [developer page](https://www.harjotrana.com/developers) documents every surface described here, and the contact options are one `curl https://www.harjotrana.com/api/v1/contact-options` away.

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Locum: Giving Grok Bot a Stand-In

Canonical URL: https://www.harjotrana.com/blog/locum-grok-bot-provider-adapter
Author: Harjot Singh Rana
Published: 2026-08-22
Reading time: 20 min

> Grok Bot can burn a week of quota in an hour. Locum hands the work to the Claude Code or Codex CLI already logged in on your machine, without ever touching a credential.

I burned 20% of a weekly Grok Bot allowance in thirty minutes. Seven or eight bots running in parallel, all doing real work, all metering hard. The product is genuinely good; that was the frustrating part. The bottleneck wasn't capability. It was inference budget.

I already pay for Claude and for ChatGPT. Those subscriptions have their own limits, and they were sitting completely idle while Grok Bot ground through its quota.

So I built **Locum**, a provider adapter that lets Grok Bot delegate coding work to the agent CLIs already authenticated on my own machine. A locum is a qualified professional who temporarily does someone else's job, which is exactly the arrangement.

This post is about how it works, and more importantly about the one architectural decision that separates it from the version of this idea that gets you banned.

## The constraint that decides everything

Grok Bot doesn't run on your laptop. Each bot gets a persistent computer in xAI's cloud, which is what makes it useful: it keeps working after you close the lid. It also means the bot **cannot see `localhost`**. There is no local plugin surface to hook into.

What it does have is documented: custom MCP connectors. You point it at a public HTTPS endpoint speaking the Model Context Protocol, it discovers your tools, and it can call them. xAI even publishes a guide for tunnelling a server running on your own machine.

That single fact fixes the shape of the whole thing. Locum has to be a remote MCP server that reaches back to your machine, not a patched binary and not an intercepted protocol.

## The line I would not cross

There are two ways to build "use your Claude subscription somewhere else," and they are not close to each other.

The tempting one: read the OAuth token that Claude Code stores, and call Anthropic's API yourself with it. It works. It is also explicitly prohibited. Anthropic's own [Claude Code legal page](https://code.claude.com/docs/en/legal-and-compliance) says OAuth is "intended exclusively" for ordinary use of Claude Code and other native Anthropic applications, and that they do not permit third-party developers to route requests through Free, Pro, or Max plan credentials on behalf of their users. People have shipped tools on the other side of that line and watched them get shut off.

The other way: don't handle credentials at all. Spawn the official binary.

*Diagram: Locum spawns the official claude binary as a subprocess. Only that binary talks to Anthropic, using its own stored credentials. Locum itself has no path to the vendor API.*

*The credential boundary. Locum is a launcher, not a proxy.*

Locum runs `claude -p "<prompt>"` as a subprocess. That's it. The CLI you already logged into does the authenticating, exactly as it does when you type the command yourself. Locum never opens `~/.claude`, never touches the keychain, never sees a token, and never makes a request to `api.anthropic.com`.

Four invariants are written at the top of the source file, and they're the reason the project is publishable:

1. Never read credential files, keychains, or OAuth tokens.
2. Never call `api.anthropic.com` or `api.openai.com` directly.
3. Only ever spawn the official `claude` / `codex` binaries.
4. Single-operator. One token, one allowlist of workspace roots.

One more thing it deliberately does **not** do: bypass Grok Bot's own entitlement. You still need Grok Bot access for a connector to exist at all. Locum changes where the inference comes from. It does not get you Grok Bot for free, and I'd have no interest in shipping the version that did.

## The shape of it

*Diagram: Grok Bot and the Cloudflare tunnel sit above a dashed boundary marking xAI's cloud. Below the boundary, on your own machine, Locum receives the tunnelled request and spawns either the Claude Code CLI or the Codex CLI.*

*Six tools cross that boundary: delegate_to_claude, resume_claude, delegate_to_codex, check_job, list_jobs, cancel_job.*

About a thousand lines of Python across two files, no framework beyond an MCP server library.

## Async is the entire trick

This is the part that decides whether the integration works at all, and it's easy to get wrong.

MCP tool calls are request/response with a timeout measured in tens of seconds. Real coding tasks take minutes. If `delegate_to_claude` tried to run the task and return the answer, every useful call would time out, and worse, it would time out *after* burning the work, with the result stranded in a dead subprocess.

*Diagram: A synchronous tool call is cut off by the MCP timeout while the coding task is still running, so the result is discarded. The asynchronous version returns a job id immediately and the bot polls until the job reports done.*

*Build this in from the start. Retrofitting it means rewriting the server.*

So `delegate_to_claude` starts the job and returns a handle immediately. The bot polls `check_job` until the status changes.

The polling is more useful than it sounds. Locum runs the CLI with `--output-format stream-json` and parses the event stream as it arrives, so a poll returns not just "still running" but the turn count and the last few tool calls:

```json
{
  "job_id": "dacb5836aa66",
  "status": "running",
  "elapsed_seconds": 74.2,
  "turns": 9,
  "recent_activity": ["Read src/auth.ts", "Edit src/auth.ts", "Bash npm test"]
}
```

The bot can narrate real progress instead of staring at a black box. When the job finishes, the result comes back along with a `session_id`, which matters, because `resume_claude` reuses it. A cold delegation re-pays about 18k tokens of `CLAUDE.md` and system prompt setup; resuming hits the prompt cache instead. Follow-ups should always resume.

## Grok speaks OAuth, and only OAuth

I built the first version with a bearer token. Simple, adequate for a single-operator tool, easy to test with `curl`.

Then I opened Grok's custom connector dialog and found no field for a header. It had probed my server, got a bare `401`, and fallen back to asking me for OAuth app credentials: client ID, authorization endpoint, token endpoint, PKCE method.

That's the MCP specification's authorization flow, and it isn't optional. A remote MCP server that wants a real client has to be an OAuth 2.1 authorization server.

So Locum became one. It's less code than it sounds:

*Diagram: OAuth sequence: Grok probes the MCP endpoint and receives a 401 pointing at discovery metadata, registers itself dynamically, sends the operator to a consent screen gated by a passphrase, exchanges the code with PKCE for a token, and then calls tools.*

*Discovery, dynamic registration, PKCE, refresh. No third-party OAuth app to create.*

The consent screen is the load-bearing part, and it's worth being explicit about why. `/authorize` sits on a public tunnel. Without a gate, anyone who learned the URL could walk through the flow, mint themselves a token, and get shell access to my machine. So the consent page demands a passphrase and shows the redirect target before you approve. If that destination isn't the one you expect, you're looking at someone else's authorization attempt.

Codes are single-use and expire in 120 seconds. `S256` is required; `plain` is refused. Every comparison is constant-time. There's a test file that exercises all of it: discovery, registration, the consent gate, PKCE enforcement, replay rejection, refresh. Security code that isn't tested is decoration.

## Three bugs worth your time

The architecture took an afternoon. These took longer.

### The 404 that was a port collision

Everything started returning `404`. Every path, including `/health`, which is an unconditional route. The tunnel reported healthy, ingress validated, the routing rule matched.

The response was `content-length: 10`. Exactly the length of `Not found.`, and that string wasn't in my server.

*Diagram: Two processes listen on port 8787: Locum on IPv4 127.0.0.1 and the Grok Bot desktop app on IPv6 colon-colon-1. macOS resolves localhost to the IPv6 address first, so traffic reaches the wrong process.*

*The Grok Bot app itself listens on port 8787. macOS prefers ::1, so the tunnel talked to it instead.*

The Grok Bot desktop app listens on `[::1]:8787`. Locum was on `127.0.0.1:8787`. Same port, different address families, so there was no "address already in use" error, no warning, nothing. macOS resolves `localhost` to `::1` before `127.0.0.1`, so every tunnelled request reached the wrong process.

Two things found it. `lsof -nP -iTCP:8787 -sTCP:LISTEN` showed both listeners, one line each. And `cloudflared --loglevel debug` logs `ingressRule=` and `originService=` per request, which separates "cloudflared's catch-all matched" from "the origin returned this". I'd assumed the former and was wrong.

Two fixes, because either alone leaves a trap: move to port 8791, and target `127.0.0.1` explicitly rather than `localhost`.

### The service daemon that installed itself broken

`sudo cloudflared service install` writes a launch daemon whose `ProgramArguments` is just the binary path. No `tunnel run`. No `--config`. It runs as root, whose `$HOME` is `/var/root`, so `~/.cloudflared/config.yml` is invisible to it, and it crash-loops on a 5-second `KeepAlive`.

The failure hides itself perfectly: the tunnel keeps working, because your user-level `cloudflared tunnel run` is still serving. You end up with a crash-looping root daemon and a working user process registered as two connectors for one tunnel. Adding a connector while that's in flux is how you get "Connection failed" from a server that answers every external probe.

The fix is to put the config where root can read it, `/etc/cloudflared`, and rewrite `credentials-file` to match.

### The nested-session auth failure

Every delegated job died with `Failed to authenticate: OAuth session expired and could not be refreshed`.

Claude Code exports session plumbing into every child process it spawns: `CLAUDECODE`, a family of `CLAUDE_CODE_*` variables, and `ANTHROPIC_BASE_URL`. A `claude` that inherits those believes it's a nested child session and tries to delegate authentication to a host socket that isn't listening.

Locum now strips them before spawning, but only when it detects it's nested, so a deliberately-set `ANTHROPIC_BASE_URL` still works in a normal terminal.

The lesson generalises past this project: when you spawn a CLI from a server, you inherit an environment you did not design. Decide what crosses that boundary.

## Four things I only learned by using it

The version above worked. Using it for a day surfaced the rest.

### An approval prompt is not a safety feature, it is a hang

Jobs involving `gh`, or an install, or anything else the agent wanted to confirm
would simply stop. Not fail: stop. The client sat waiting on a job that would
never finish, because the approval prompt was on my Mac and the thing that asked
for it was a bot in someone else's cloud.

A prompt nobody can answer does not protect you. It just converts a task into a
timeout. Delegated jobs now run with `--dangerously-skip-permissions` and
`--dangerously-bypass-approvals-and-sandbox`, and the honest consequence is that
the token and a narrow workspace allowlist are what protect you, not the agent's
permission model. The allowlist bounds where a job starts, not where a shell
command it runs can reach. That trade is written down in `SECURITY.md` rather
than glossed, because the previous version of that file said the opposite.

### The model and the effort should be a decision, not a default

Some delegations want the cheapest model that can follow instructions. Some want
the deepest reasoning available, once. So every delegate tool takes `model` and
`effort`, under one vocabulary across both CLIs: Claude spells it `--effort`,
Codex spells it `-c model_reasoning_effort` and requires that `-c` precede the
subcommand. A caller should not have to know either fact.

The interesting part is `resume_claude` taking them too. Escalating a *resumed*
session keeps the context you already paid for, so "start cheap, escalate on the
turn that actually needs it" costs far less than restarting at a higher setting.

### Correct answers hide broken telemetry

Codex delegations returned the right results, so it looked fine. It was not.
The progress parser was reading an event stream Codex no longer emits, so turn
counts sat at zero and the activity feed showed the literal string
`item.completed` instead of what the agent was doing.

Worse, Codex can fail a turn and still exit 0. Those jobs were reported as
`done` with whatever happened to be left in the output file. Silently wrong
results are worse than visible failures, and only testing the thing I had not
launched with caught it.

### You cannot demo a claim you cannot see

The MCP client shows a chat bubble. That is all. There was no way to show that
work ran on my machine, what the agent did, or what it cost, short of grepping a
log and matching line numbers against the real file.

So the server narrates every job on stdout, and there is a dashboard: every
session with model, effort, turns, duration, tokens and cost, and a full
transcript per job of each tool call with its arguments, the reasoning, and the
output. Running jobs stream in live.

*Diagram: Three consumers read one recorded activity stream: the MCP client's check_job response, the dashboard transcript, and the server's stdout log.*

*All three read one recorded stream, so they cannot drift apart.*

Then the dashboard hung at "connecting" behind Cloudflare while working
perfectly on localhost. The stream sent its response headers and waited up to
twenty seconds for the first event; a proxy holds headers until some body
arrives, so the browser never saw a response at all and `EventSource.onopen`
never fired. Flushing a single comment immediately fixed it. Time to first byte
through the tunnel went from never to 0.4 seconds.

That is the third bug in this project that only existed behind the proxy, after
the port collision and the OAuth callback. Anything streaming or long-lived has
to be tested through the tunnel, because a proxy changes the semantics, not just
the latency.

## The tool found its own bugs

Once the dashboard existed, the obvious thing to try was pointing Locum at its
own newest code. I delegated a review of the Codex event parser, written about
an hour earlier, at `model: opus, effort: high`.

It came back with five findings. All five were real.

The dangerous one: the parser called `.get()` on whatever `json.loads` returned.
A bare string, a number, a list are all valid JSON, so a line like `"error"`
raised `AttributeError`, which killed the stdout reader. The runner then skipped
both `proc.wait()` and its cleanup step, leaving the Codex child unreaped and
its temp file on disk. On a server designed to run for weeks under launchd.

I reproduced all five before fixing them. The subtlest was that a mid-stream
error flipped a job out of `running` immediately, which broke cancellation, let
the history pruner evict a job that was still executing, and masked a later
success.

Later, during the demo recording, a delegated job added a `--version` flag to
the server and ran the test suite to confirm nothing broke. Its summary of what
it changed matched the diff. It did put the check at the top of `__main__`,
which is after the module-level token guard has already exited, so
`server.py --version` refused to answer without a secret. That one I fixed by
hand.

Both of those are the honest version of what this tool is for: it is very good,
it is not unsupervised, and the difference matters.

## What it actually saves

Honesty matters more than the pitch here, so:

**It reduces Grok Bot usage. It does not eliminate it.** The bot's own orchestration turns still meter. The win is collapsing fifty bot steps into one tool call and a few polls, moving the long agentic grind onto the subscription you already pay for.

**Your machine has to be awake with the tunnel up.** It's a personal tool, not a service.

**Those subscriptions have limits too.** This changes where the inference budget comes from. Nobody found an infinite-token glitch.

**The skill file matters more than the tools.** Without an instruction telling the bot to *prefer* delegating, it has the tools available and keeps grinding through its own loop anyway. That one markdown file is the difference between the integration working and merely existing.

## Running it

It's open source. You need `claude` or `codex` already signed in, a domain on Cloudflare for a stable hostname, and Grok Bot access.

```bash
git clone https://github.com/HarjjotSinghh/locum
cd locum && cp .env.example .env    # set a token and your workspace roots
set -a && source .env && set +a
uv run server.py
```

Then a tunnel, a custom connector pointed at `https://your-host/mcp`, and the skill file pasted into a Grok Bot Skill.

The invariants at the top of `server.py` aren't decoration. If a change would break one of them, it's the wrong change, and that is what keeps this a tool worth using rather than a liability worth avoiding.

*Not affiliated with or endorsed by xAI, Anysphere, OpenAI, or Anthropic.*

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# The Practical Vercel Turborepo Cheatsheet

Canonical URL: https://www.harjotrana.com/blog/turborepo-cheatsheet
Author: Harjot Singh Rana
Published: 2025-12-08
Reading time: 23 min

> Task graphs, remote caching, and the pipeline config that actually keeps a monorepo fast.

**Turborepo** is a build system optimized for JavaScript and TypeScript monorepos, written in Rust. It handles task orchestration, caching, and dependency management across multiple packages in a single repository.

---

## Core Concepts

### Monorepo Structure

```text
my-monorepo/
├── apps/
│   ├── web/
│   ├── mobile/
│   └── admin/
├── packages/
│   ├── ui-library/
│   ├── utils/
│   └── types/
├── turbo.json (configuration)
├── package.json (root workspace)
└── pnpm-workspace.yaml (or package.json for npm/yarn workspaces)
```

### Key Concepts

- **Package Graph**: Relationship between all packages in the monorepo
- **Task Graph**: Dependency relationships between tasks
- **Caching**: Turborepo remembers task outputs and reuses them when inputs haven't changed
- **Remote Caching**: Share cache artifacts across team members and CI/CD systems

---

## Installation & Setup

### Initialize a New Turborepo

```bash
npx create-turbo@latest
```

### Add Turborepo to Existing Project

```bash
npm install turbo --save-dev

# Initialize configuration
turbo init
```

---

## turbo.json Configuration

### Basic Structure

```json
{
  "extends": ["//"],
  "globalDependencies": ["*.env", "package.json"],
  "globalEnv": ["NODE_ENV"],
  "globalPassThroughEnv": ["HOME", "PATH"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"],
      "cache": true,
      "env": ["NODE_ENV", "API_*"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true,
      "interactive": true
    },
    "lint": {
      "outputs": []
    }
  }
}
```

### Task Definition Options

| Option | Description | Example |
|--------|-------------|---------|
| `dependsOn` | Tasks that must complete first | `["^build"]`, `["build", "lint"]` |
| `outputs` | Files to cache | `["dist/**", ".next/**"]` |
| `cache` | Enable/disable caching | `true` or `false` |
| `env` | Environment variables that affect hashing | `["NODE_ENV", "API_*"]` |
| `passThroughEnv` | Environment variables available but not cached | `["HOME"]` |
| `inputs` | Files to consider for cache invalidation | `["src/**", "package.json"]` |
| `persistent` | Mark as long-running (dev servers) | `true` |
| `interactive` | Accept stdin input | `true` |
| `interruptible` | Allow restart by turbo watch | `true` |
| `outputLogs` | Log verbosity | `"full"`, `"hash-only"`, `"new-only"`, `"errors-only"`, `"none"` |

### Dependency Prefix Symbols

| Symbol | Meaning | Example |
|--------|---------|---------|
| `^` | Depends on same task in dependencies | `"^build"` (wait for deps to build) |
| No prefix | Same-package dependency | `"build"` (in test task = test after build in same pkg) |
| `package#task` | Specific package task | `"utils#build"` (utils package build task) |

---

## Common turbo.json Patterns

### Pattern 1: Build with Dependencies

```json
{
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    }
  }
}
```

Builds dependencies first, then this package.

### Pattern 2: Test Before Build

```json
{
  "pipeline": {
    "build": {
      "dependsOn": ["test", "^build"],
      "outputs": ["dist/**"]
    }
  }
}
```

Runs local tests, then dependency builds, then this package build.

### Pattern 3: Cache-Only Tasks

```json
{
  "pipeline": {
    "test": {
      "cache": true,
      "outputs": ["coverage/**"]
    }
  }
}
```

Tests are cached; only re-run on source code changes.

### Pattern 4: Never Cache

```json
{
  "pipeline": {
    "deploy": {
      "cache": false
    }
  }
}
```

Useful for deployment and external API calls.

### Pattern 5: Long-Running Tasks (Dev Server)

```json
{
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true,
      "interactive": true
    }
  }
}
```

Dev servers run continuously and accept input.

---

## CLI Commands

### Run Tasks

```bash
# Run a single task in all packages
turbo run build

# Run multiple tasks
turbo run build lint test

# Run task in specific package
turbo run build --filter=ui

# Run without dependencies (immediate task only)
turbo run test --only

# Run only affected packages (git-based)
turbo run build --affected

# Run with all environment variables available
turbo run build --env-mode=loose

# Run in parallel (ignoring dependency graph)
turbo run dev --parallel

# Force re-run (ignore cache)
turbo run build --force

# Run with verbose output
turbo run build -v
turbo run build -vv   # more verbose
turbo run build -vvv  # very verbose
```

### Filtering

```bash
# By package name
turbo run build --filter=web

# By directory pattern
turbo run build --filter=./apps/*

# By git changes
turbo run build --filter=[HEAD^1]

# Select dependents (packages depending on target)
turbo run build --filter=...ui

# Select dependencies (packages target depends on)
turbo run build --filter=ui...

# Exclude packages
turbo run build --filter=!admin

# Combine filters (union)
turbo run build --filter=web --filter=mobile

# Specific task in package
turbo run web#build
```

### Cache Management

```bash
# Disable caching
turbo run build --no-cache

# Use only local cache
turbo run build --cache=local:rw

# Use only remote cache
turbo run build --cache=remote:rw

# Read-only cache
turbo run build --cache=local:r

# Disable cache completely
turbo run build --cache=local:,remote:
```

### Other Useful Commands

```bash
# Dry run (show what would execute, don't run)
turbo run build --dry-run

# Dry run as JSON
turbo run build --dry=json

# Show task execution order without running
turbo run build --graph=mermaid

# Generate performance trace (Chrome Tracing format)
turbo run build --profile -vv

# Generate run summary (metadata about execution)
turbo run build --summarize

# Continue on error
turbo run build --continue=always

# Limit concurrency
turbo run build --concurrency=4
turbo run build --concurrency=50%

# Watch mode (watch for changes and re-run)
turbo watch run build
```

### Remote Caching (Vercel)

```bash
# Login to Vercel
turbo login

# Link to remote cache
turbo link

# Link specific project in package
cd apps/web && turbo link

# Logout
turbo logout

# Verify remote cache is working
turbo run build --summarize
```

### Utility Commands

```bash
# List all packages in workspace
turbo ls

# Check workspace info
turbo info

# Check package boundaries/dependencies
turbo boundaries

# Generate new package/code
turbo gen

# Prune dependency graph (for Docker/CI)
turbo prune --scope=web

# Check for missing environment variables
turbo scan

# Get Turborepo version
turbo --version
```

---

## Environment Variables

### Global Environment Variables (turbo.json)

```json
{
  "globalEnv": ["NODE_ENV", "API_KEY"],
  "globalPassThroughEnv": ["HOME", "PATH"]
}
```

- `globalEnv`: Changes invalidate ALL task caches
- `globalPassThroughEnv`: Available to tasks but don't affect caching

### Task-Specific Environment Variables

```json
{
  "pipeline": {
    "build": {
      "env": ["NODE_ENV", "NEXT_PUBLIC_*"],
      "passThroughEnv": ["HOME"]
    }
  }
}
```

### Environment Variable Patterns

```json
{
  "env": [
    "*",                 // All variables
    "API_*",            // Prefix match
    "!API_SECRET",      // Negation (exclude)
    "NODE_ENV"          // Exact match
  ]
}
```

### Strict vs Loose Mode

```bash
# Strict mode (default) - only listed vars available
turbo run build --env-mode=strict

# Loose mode - all vars available (less safe for caching)
turbo run build --env-mode=loose
```

---

## Package Configurations

Use for task customization per-package:

```json
// packages/ui/turbo.json
{
  "extends": ["//"],
  "pipeline": {
    "build": {
      "outputs": ["dist/**"],
      "env": ["THEME_*"]
    }
  }
}
```

---

## Common Patterns & Best Practices

### 1. Workspace Dependencies

```json
// apps/web/package.json
{
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*"
  }
}
```

### 2. Shared Configuration

```json
// Root turbo.json
{
  "globalDependencies": [
    "package.json",
    "pnpm-lock.yaml",
    ".env.example"
  ]
}
```

### 3. CI/CD Optimization

```bash
# In GitHub Actions
turbo run build --affected --cache=remote:rw

# In other CI systems
turbo run build --force --cache=local:rw
```

### 4. Docker/Monorepo Pruning

```bash
# Prune only dependencies needed for `web` app
turbo prune --scope=web --docker

# Then in Dockerfile
COPY --from=base /repo/out/full /repo
COPY --from=base /repo/out/json . 
COPY --from=base /repo/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
```

### 5. Task Skipping (turbo-ignore)

```bash
# In CI environment variable
TURBO_TELEMETRY_DISABLED=1

# Skip build if only docs changed
turbo-ignore docs
```

---

## Caching Strategy

### What Gets Cached

- **Outputs**: Files specified in `outputs` key
- **Logs**: Task logs (always cached if caching enabled)
- **Metadata**: Task execution metadata

### Cache Invalidation

Cache is invalidated when:

- Source files in `inputs` change
- `globalDependencies` files change
- Environment variables in `env` change
- `turbo.json` changes
- `package.json` changes (in that package)
- Lockfile changes

### Cache Locations

```bash
# Local cache (default)
.turbo/cache/

# Custom cache directory
turbo run build --cache-dir=.turbo-cache

# Or in turbo.json
{
  "cacheDir": ".turbo-cache"
}
```

---

## Debugging & Troubleshooting

### Check Task Configuration

```bash
# See what tasks will execute
turbo run build --dry-run

# Get detailed execution info
turbo run build --dry=json | jq

# Verbose output
turbo run build -vvv
```

### Analyze Performance

```bash
# Generate Chrome trace
turbo run build --profile -vv

# Check hash and dependencies
turbo run build --dry-run | grep -A5 "web#build"

# See execution summary
turbo run build --summarize
# View in `.turbo/runs/` directory
```

### Common Issues

| Issue | Solution |
|-------|----------|
| Cache not working | Check `outputs` glob pattern; verify env vars in `env` key |
| Task not running | Check `dependsOn`; use `--dry-run` to verify task graph |
| Environment vars not available | Add to `env` in turbo.json for hashing, `passThroughEnv` for availability |
| Performance degradation | Check if inputs are too broad; optimize glob patterns |
| Package not found | Verify `package.json` name; use `turbo ls` to list packages |

---

## Advanced Tips

### 1. Framework Inference

Turborepo automatically includes framework-specific env vars:

- Next.js: `NEXT_PUBLIC_*` (automatic)
- Nuxt: `NUXT_*` (automatic)
- Vue: `VUE_APP_*` (automatic)

### 2. Multiple Package Managers

```json
{
  "packageManager": "pnpm@8.0.0"
}
```

### 3. Workspace Configuration

```json
// pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
  - 'tools/*'
```

```json
// package.json (npm/yarn)
{
  "workspaces": ["apps/*", "packages/*"]
}
```

### 4. Remote Cache with Custom Endpoint

```json
{
  "remoteCache": {
    "apiUrl": "https://custom-cache.com",
    "signature": true,
    "timeout": 60,
    "uploadTimeout": 120
  }
}
```

### 5. Concurrency Control

```bash
# Serial execution (1 at a time)
turbo run build --concurrency=1

# Use 50% of CPU cores
turbo run build --concurrency=50%

# Or in turbo.json
{
  "concurrency": "50%"
}
```

---

## Real-World Example

```json
{
  "extends": ["//"],
  "globalDependencies": ["*.env", "package.json"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"],
      "cache": true,
      "env": ["NODE_ENV", "API_URL"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "cache": true,
      "outputs": []
    },
    "type-check": {
      "cache": true,
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true,
      "interactive": true
    },
    "deploy": {
      "cache": false,
      "dependsOn": ["build", "test", "lint"]
    }
  }
}
```

### Usage:

```bash
# Full CI pipeline
turbo run lint type-check test build

# Development
turbo run dev --parallel

# Deploy only changed packages
turbo run build --affected && turbo run deploy

# Rebuild everything
turbo run build --force
```

---

## Resources

- **Official Docs**: https://turborepo.com
- **API Reference**: https://turborepo.com/docs/reference
- **Examples**: https://github.com/vercel/turborepo/tree/main/examples
- **Vercel Remote Cache**: https://vercel.com/docs/monorepos/turborepo

---

## Quick Command Reference

```bash
# Development
turbo run dev --parallel

# Build
turbo run build

# Lint
turbo run lint

# Test
turbo run test

# All checks before commit
turbo run lint type-check test

# Force rebuild everything
turbo run build --force

# Only affected packages
turbo run build --affected

# Specific package
turbo run build --filter=web

# Watch mode
turbo watch run build

# See what would run
turbo run build --dry-run

# Performance analysis
turbo run build --profile -vv

# Remote cache
turbo login
turbo link
turbo run build
```

_Last Updated: December 2025 | Based on Turborepo latest documentation_

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# uv and Ruff: A Practical Python Tooling Cheatsheet

Canonical URL: https://www.harjotrana.com/blog/uvx-and-ruff-cheatsheet
Author: Harjot Singh Rana
Published: 2025-10-08
Reading time: 7 min

> Replacing pip, venv, black, and flake8 with two Rust binaries, and what changes day to day.

Quick reference for common UV and Ruff commands.

## UV Commands

### Environment Management

```bash
# Create virtual environment
uv venv

# Activate environment
source .venv/bin/activate  # macOS/Linux
.venv\Scripts\activate     # Windows

# Create with specific Python version
uv venv --python 3.11
```

### Package Management

```bash
# Install from pyproject.toml
uv pip install -e .
uv pip install -e ".[dev]"  # Include dev dependencies

# Install specific package
uv pip install fastapi
uv pip install "fastapi>=0.109.0"

# Install from requirements.txt
uv pip install -r requirements.txt

# Uninstall package
uv pip uninstall fastapi

# List installed packages
uv pip list

# Show package info
uv pip show fastapi
```

### Running Commands

```bash
# Run Python script
uv run python script.py

# Run module
uv run -m pytest

# Run with specific Python version
uv run --python 3.11 python script.py
```

### Cache Management

```bash
# Clear cache
uv cache clean

# Show cache directory
uv cache dir
```

## Ruff Commands

### Linting

```bash
# Check all files
uv run ruff check .

# Check specific files/directories
uv run ruff check app/ tests/

# Auto-fix issues
uv run ruff check --fix .

# Show all issues (including fixed)
uv run ruff check --show-fixes .

# Watch mode (re-check on file changes)
uv run ruff check --watch .
```

### Formatting

```bash
# Format all files
uv run ruff format .

# Format specific files
uv run ruff format app/main.py

# Check formatting without changing files
uv run ruff format --check .

# Show diff of what would change
uv run ruff format --diff .
```

### Configuration

```bash
# Show current configuration
uv run ruff config

# Show rule documentation
uv run ruff rule E501

# List all available rules
uv run ruff linter
```

## Common Workflows

### Initial Setup

```bash
cd auto-mt
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
```

### Daily Development

```bash
# Format and fix issues before committing
uv run ruff format .
uv run ruff check --fix .

# Run tests
uv run pytest

# Start dev server
uv run uvicorn app.main:app --reload
```

### Pre-Commit Check

```bash
# Check everything
uv run ruff check .
uv run ruff format --check .
uv run pytest
```

### Adding Dependencies

```bash
# Add to pyproject.toml dependencies list, then:
uv pip install -e ".[dev]"

# Or install directly (but remember to update pyproject.toml!)
uv pip install new-package
```

## VS Code Integration

With the Ruff extension installed and `.vscode/settings.json` configured:

- **Auto-format on save**: Enabled
- **Auto-fix on save**: Enabled
- **Import sorting on save**: Enabled
- **Ruler at 100 chars**: Visible guide

### Keyboard Shortcuts

- `Cmd/Ctrl + S`: Save and auto-format
- `Cmd/Ctrl + Shift + P`  ->  "Format Document": Manual format
- `Cmd/Ctrl + Shift + P`  ->  "Organize Imports": Manual import sort

## Ruff Rules Reference

Common rule categories enabled in this project:

- **E**: pycodestyle errors (e.g., E501 line too long)
- **W**: pycodestyle warnings
- **F**: pyflakes (e.g., F401 unused import)
- **I**: isort (import sorting)
- **B**: flake8-bugbear (common bugs)
- **C4**: flake8-comprehensions (list/dict comprehensions)
- **UP**: pyupgrade (modern Python syntax)

### Example Fixes

```python
# Before
from typing import List
import os
import sys

def foo(x: List[int]) -> List[int]:
    return [i for i in x if i > 0]

# After (Ruff auto-fixes)
import os
import sys

def foo(x: list[int]) -> list[int]:
    return [i for i in x if i > 0]
```

## Performance Comparison

| Tool                   | Time (large codebase) |
| ---------------------- | --------------------- |
| black + isort + flake8 | ~10s                  |
| ruff check + format    | ~0.1s                 |

| Tool           | Installation Time |
| -------------- | ----------------- |
| pip install    | ~30s              |
| uv pip install | ~1s               |

## Troubleshooting

### UV not found

```bash
# Ensure UV is in PATH
export PATH="$HOME/.cargo/bin:$PATH"
source ~/.bashrc  # or ~/.zshrc
```

### Ruff not formatting in VS Code

1. Install "Ruff" extension by Astral Software
2. Check `.vscode/settings.json` exists
3. Reload VS Code window
4. Check output panel: View  ->  Output  ->  Ruff

### Dependencies not syncing

```bash
# Clear cache and reinstall
uv cache clean
rm -rf .venv
uv venv
uv pip install -e ".[dev]"
```

## Resources

- UV Docs: https://docs.astral.sh/uv/
- Ruff Docs: https://docs.astral.sh/ruff/
- Ruff Rules: https://docs.astral.sh/ruff/rules/
- VS Code Ruff Extension: https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# From Zero to npm in 7 Hours: Building Weavestore

Canonical URL: https://www.harjotrana.com/blog/7-hours-to-mvp-weavestore
Author: Harjot Singh Rana
Published: 2025-10-05
Reading time: 7 min

> How three of us took an on-chain database SDK from idea to a published npm package in one sitting.

Yesterday was one of those rare days that reminds you why you fell in love with building things in the first place.

No hackathon. No deadline. No pressure. Just me, [@Gursagar], and [@Lovepreet] deciding to book a coworking space in Aerocity, New Delhi, and see what we could create together.

What started as a casual "let's just code and hang out" turned into one of the most productive, energizing, and memorable days of my life. By the time the sun came up, we had shipped a working MVP to NPM.

Let me take you through how it all went down.

## The Day That Kept Getting Better

### 9:00 AM - The Unlikely Start 🎓

The day began in the most un-hackathon-like way possible: sitting in an exam hall, writing my mid-semester exam. Not exactly the glamorous beginning to a productive coding session, but hey - sometimes you've got to balance the student life with the builder life.

### 1:00 PM - The Energy Shift ⚡

By afternoon, I was at the coworking space in Aerocity. Laptops open, coffee brewing, and that unmistakable feeling in the air when you know something good is about to happen.

For the next 2-3 hours, we just... built. No grand plan. No detailed spec. Just three developers in flow state, bouncing ideas off each other and turning them into code.

### 4:00 PM - The Mall Pivot ☕

Here's where it gets interesting. We decided to switch locations - moved to The Square Mall, found a quiet café, ordered more coffee, and kept the momentum going.

There's something about changing your environment that sparks creativity. New space, new energy, same unstoppable drive.

### 8:00 PM - The Night Shift Begins 🌙

As evening turned to night, we crashed at Gursagar's place. Most people would call it a day. We called it the beginning of the real work.

What happened next was pure magic - that rare state where time disappears, code flows effortlessly, and every problem has a solution that reveals itself just when you need it.

## The Result: WeaveStore

**Within 7 hours of focused building, we created and shipped WeaveStore** - an SDK that allows developers to create and manage databases directly on-chain, powered by IPFS and IPNS.

### What Makes WeaveStore Different?

Traditional databases live on centralized servers. WeaveStore flips that model:

- **On-Chain Database Management**: Create and manage databases directly on the blockchain
- **IPFS-Powered Storage**: Decentralized, permanent, and censorship-resistant data storage
- **IPNS for Dynamic Updates**: Mutable pointers to immutable content
- **Developer-First API**: Simple, intuitive interface that feels familiar to any developer

### From Idea to NPM in Record Time

By dawn, we had:
- ✅ Core SDK functionality working
- ✅ Documentation written
- ✅ Package published to NPM
- ✅ First version live and ready for developers to use

📦 **Check it out**: [WeaveStore on NPM](https://www.npmjs.com/package/weavestore) 

## The Morning After Reality Check

Today, I'm back home - preparing for another exam while building adapters and improvements for WeaveStore. The contrast is surreal: one moment you're shipping production code at 3 AM, the next you're back to textbooks and lecture notes.

But that's the beauty of being a student developer. You get to live in both worlds - the structured academic environment and the chaotic, creative world of building products.

## What Made This Day Different?

Looking back, I can pinpoint exactly why this day was so productive when others fall flat:

### 1. The Right People Change Everything 🤝

Solo coding is great for deep focus, but collaborative building hits different. When you're working with people who:
- Get excited about the same problems
- Challenge your ideas constructively
- Fill in your knowledge gaps
- Keep the energy high when motivation dips

...you unlock a level of productivity that caffeine alone could never achieve.

### 2. Momentum Is Contagious ⚡

There's a compounding effect when a team is in sync. One person solves a problem, which unblocks another person, which sparks an idea for the third person. Before you know it, you're not just building features - you're building on each other's momentum.

### 3. Environment Matters More Than You Think 🏢

The coworking space wasn't just a change of scenery - it was a psychological shift. Being in a space designed for productivity, surrounded by other people building things, creates an ambient pressure to perform.

Then switching to the mall café added variety without breaking flow. Sometimes the best productivity hack is just... moving around.

### 4. No Burnout, Just Flow 🌊

Here's the surprising part: despite coding for 7+ hours straight, I never felt burned out. Why?

Because we weren't grinding against resistance - we were riding momentum. There's a massive difference between forcing yourself to code and being pulled forward by genuine excitement about what you're building.

## The Lessons That Stick

### Teamwork > Caffeine

I've pulled plenty of all-nighters fueled by coffee and determination. But nothing compares to the sustained energy you get from working alongside people who are just as invested in the outcome as you are.

### Peer Energy Prevents Burnout

When you're solo and hit a wall, it's easy to give up. When you're with peers and hit a wall, someone else is there to say "wait, what if we tried this?" That shared problem-solving keeps burnout at bay.

### Ideas Evolve Faster in Conversation

The version of WeaveStore we shipped isn't the version we started building. Through constant conversation and iteration, the idea evolved into something better than any of us could have designed alone.

### Ship Fast, Iterate Later

We could have spent weeks planning the perfect architecture. Instead, we built something that worked, shipped it, and now we're improving it based on real usage. Sometimes done is better than perfect.

## What's Next for WeaveStore?

The MVP is live, but we're just getting started. Currently working on:

- **Framework Adapters**: Making WeaveStore work seamlessly with popular frameworks
- **Enhanced Documentation**: More examples, tutorials, and use cases
- **Performance Optimization**: Making on-chain operations even faster
- **Community Feedback**: Listening to early adopters and iterating based on real needs

## Try WeaveStore Today

If you're building decentralized applications and need a simple way to manage on-chain databases, give WeaveStore a shot:

📦 **NPM Package**: [WeaveStore](https://www.npmjs.com/package/weavestore)  

## The Bigger Takeaway

This experience reinforced something I've always believed but sometimes forget: **the right environment, the right people, and the right mindset can compress months of work into a single day.**

You don't need a hackathon. You don't need a deadline. You don't even need a fully formed idea.

What you need is:
- People who energize you
- A problem worth solving
- The willingness to just start building

Everything else figures itself out along the way.

## Let's Build Together

This day wouldn't have been possible without [@Gursagar] and [@Lovepreet]. If you're working on something interesting or just want to connect with fellow builders, hit me up:

- **GitHub**: [HarjjotSinghh](https://github.com/HarjjotSinghh)
- **LinkedIn**: [HarjjotSinghh](https://www.linkedin.com/in/HarjjotSinghh)
- **Twitter/X**: [HarjjotSinghh](https://x.com/HarjjotSinghh)

I'm always down to connect with people who love building things. Whether you want to collaborate, share ideas, or just chat about tech - my DMs are open.

## Final Thoughts

Would I do this again? **Absolutely. 10/10 experience.**

In fact, I'm already thinking about the next one. Because days like this remind you that building software isn't just about the code - it's about the people, the energy, and the shared experience of creating something from nothing.

So here's my challenge to you: **Find your people. Book that space. Build that thing you've been thinking about.**

You might be surprised what you can accomplish in just one day.

**Happy building!** 🚀

---

*Built in 7 hours. Shipped at dawn. Still can't believe it happened. If you try WeaveStore or have questions, reach out - I'd love to hear from you.*

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Building Zyflo: An Accessible, Motion-First React Library

Canonical URL: https://www.harjotrana.com/blog/building-zyflo-ui-library
Author: Harjot Singh Rana
Published: 2025-09-23
Reading time: 6 min

> Why the existing component libraries kept failing me, and the constraints I built Zyflo around.

![Zyflo UI Library](https://zyflo.vercel.app/og.png)

## The Problem That Started It All

Picture this: You're deep into a client project, deadline looming, and you're on the hunt for that perfect UI component. You know the one  -  visually stunning, smoothly animated, and ready to drop into your codebase without a headache. Sound familiar?

I found myself in exactly this situation while freelancing on a project for one of my clients. For 2-3 days straight, I scoured the internet, diving deep into component libraries, GitHub repositories, and design systems. But every time I thought I'd found "the one," reality hit me with one of these scenarios:

### The Three Frustrations Every Developer Knows

**a) Beautiful but Static** 🎨  
I'd discover visually appealing components that looked amazing in screenshots but lacked any animation or interactive feedback. Sure, they were pretty, but they felt lifeless  -  like a sports car without an engine.

**b) Animated but Ugly** ⚡  
Then there were the animated components that moved beautifully but looked like they were designed in 2010. The animations were smooth, but the aesthetics? Let's just say they wouldn't win any design awards.

**c) Complex Configuration Hell** 🔧  
Finally, I'd stumble upon components that were both beautiful AND animated, but configuring them required a PhD in that specific library's documentation. Want to change a color? Good luck finding the right prop among 47 different configuration options.

## The Indie Hacker Solution

As any self-respecting indie hacker would do when faced with a problem, I thought: *"Fine, I'll build it myself."*

But this wasn't just about solving my immediate problem. I saw an opportunity to create something that addressed all three pain points I'd experienced. Something that would save other developers from the same frustrating component hunt I'd just endured.

## Enter Zyflo: Where Design Meets Motion

And so, **Zyflo** was born  -  a UI library designed to make your interface "flow" seamlessly and effectively. The name itself embodies the philosophy: smooth, effortless, and natural movement in user interfaces.

### The Three Pillars of Zyflo

When building Zyflo, I established three non-negotiable principles:

#### 1. **Animated by Default** ⚡
Every component comes with thoughtful animations built-in. No more spending hours trying to figure out how to make a button feel responsive or a modal appear smoothly. The animations aren't just eye candy  -  they provide meaningful feedback and guide user attention.

#### 2. **Effortlessly Configurable** 🎛️
Customization should be intuitive, not intimidating. Whether you want to change colors, adjust timing, or modify behavior, Zyflo components are designed with sensible defaults and clear, predictable APIs.

#### 3. **Accessibility First** ♿
Beautiful animations mean nothing if they exclude users. Every component respects user preferences for reduced motion, includes proper ARIA labels, and follows WCAG guidelines. Because great design is inclusive design.

## The Technical Journey

Building Zyflo wasn't just about creating pretty components  -  it was about solving real architectural challenges:

### Performance Optimization
Animations can be performance killers if not implemented correctly. I spent considerable time optimizing for:
- **GPU acceleration** for smooth 60fps animations
- **Lazy loading** for components not immediately visible
- **Bundle size optimization** to keep your app fast

### Developer Experience
The library needed to feel natural to developers already familiar with modern React patterns:
- **TypeScript-first** approach with comprehensive type definitions
- **Storybook integration** for easy component exploration
- **Zero-config setup**  -  just install and start using

### Framework Agnostic Design
While built with React in mind, Zyflo's design principles and many components can be adapted to other frameworks, ensuring broader adoption and longevity.

## Real-World Impact

Since launching Zyflo, I've been amazed by the response from the developer community. The library has been used in:

- **Startup MVPs** where speed and polish matter equally
- **Enterprise applications** requiring consistent, accessible design
- **Personal projects** where developers want professional results without the overhead

## What's Next for Zyflo?

The journey is far from over. Here's what's on the roadmap:

- **More Components**: Expanding the library with data visualization, form components, and layout utilities
- **Theme System**: A comprehensive theming solution for consistent branding
- **Framework Adapters**: Official support for Vue, Svelte, and Angular
- **Design Tokens**: Integration with popular design systems and tools

## Try Zyflo Today

Ready to make your interfaces flow? Check out Zyflo and see the difference animated, accessible components can make in your projects.

🌐 **Website**: [zyflo.vercel.app](https://zyflo.vercel.app)  
⭐ **GitHub**: [github.com/HarjjotSinghh/Zyflo](https://github.com/HarjjotSinghh/Zyflo)  
📺 **Demo Video**: [Watch on YouTube](https://www.youtube.com/watch?v=ndB0QN0nQ08)

## Let's Connect and Build Together

Building Zyflo has been an incredible journey, but the best part has been connecting with fellow developers and designers who share the vision of better, more accessible web experiences.

I absolutely love making new connections and learning from the community  -  whether you're on GitHub, LinkedIn, Twitter, or any other platform. Each conversation brings new perspectives and ideas that make projects like Zyflo even better.

### Find Me Everywhere

- **GitHub**: [HarjjotSinghh](https://github.com/HarjjotSinghh)  -  Follow for updates on Zyflo and other projects
- **LinkedIn**: [HarjjotSinghh](https://www.linkedin.com/in/HarjjotSinghh)  -  Let's connect professionally
- **Twitter/X**: [HarjjotSinghh](https://x.com/HarjjotSinghh)  -  Daily thoughts on development and design
- **LeetCode**: [HarjjotSinghh](https://leetcode.com/u/HarjjotSinghh)  -  Coding challenges and solutions
- **Behance**: [harjjot](https://www.behance.net/harjjot)  -  Design portfolio and creative work
- **Discord**: [HarjjotSinghh](https://discord.com/users/826266498862415902)  -  Real-time conversations
- **Reddit**: [HarjjotSinghh](https://www.reddit.com/user/HarjjotSinghh)  -  Community discussions

### Show Some Love ❤️

If Zyflo resonates with you or solves a problem you've faced, I'd be incredibly grateful if you could:

- ⭐ **Star the repository** on GitHub  -  it helps others discover the project
- 🐦 **Follow me** on your preferred social platform for updates and new projects
- 💬 **Share your experience**  -  I love hearing how Zyflo is being used in real projects
- 🤝 **Contribute**  -  whether it's code, documentation, or just feedback, every contribution matters

## The Bigger Picture

Zyflo represents more than just another UI library  -  it's a testament to the power of the indie developer community. When we encounter problems, we don't just complain; we build solutions. We share our work, learn from each other, and collectively push the web forward.

Every star on GitHub, every follow on social media, and every developer who chooses to use Zyflo in their project validates this approach and encourages continued innovation.

So whether you're building the next unicorn startup or just tinkering with a weekend project, remember: the tools we use shape the experiences we create. Choose tools that prioritize both developer experience and user delight.

**Happy coding, and may your interfaces flow beautifully!** ✨

---

*Built with passion by an indie hacker who believes great tools should be accessible to everyone. Questions, suggestions, or just want to chat? Reach out on any platform  -  I'm always excited to connect with fellow builders.*

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Next.js and MDX: A Practical Setup Guide

Canonical URL: https://www.harjotrana.com/blog/getting-started-nextjs-mdx
Author: Harjot Singh Rana
Published: 2025-09-12
Reading time: 2 min

> Wiring MDX into the App Router so posts stay Markdown but can still render React components.

Welcome to this comprehensive guide on setting up a blog using Next.js and MDX! This powerful combination allows you to write your blog posts in Markdown while leveraging the full power of React components.

## Why Choose Next.js and MDX?

**Next.js** provides:
- Static site generation (SSG) and server-side rendering (SSR)
- Fast page loads with automatic code splitting
- Built-in routing and API routes
- Excellent developer experience with TypeScript support

**MDX** brings:
- The simplicity of Markdown syntax
- The power of React components within your content
- Reusable UI components
- Rich content capabilities

## Setting Up Your Project

First, create a new Next.js project:

```bash
npx create-next-app my-blog-app
cd my-blog-app
```

Then install the required MDX dependencies:

```bash
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx next-mdx-remote
```

## Creating Your First Blog Post

Create a new `.mdx` file in your content directory:

```mdx
---
title: "My First Blog Post"
date: "2025-09-12"
author: "Your Name"
---

# Hello World!

This is my first blog post using MDX!
```

## Using React Components in MDX

One of the best features of MDX is the ability to use React components directly in your content:

```mdx
import { Alert } from '@/components/ui/alert';

<Alert variant="info">
  This is a custom React component inside my MDX content!
</Alert>
```

## Conclusion

Next.js + MDX provides an excellent foundation for modern blog development. You get the best of both worlds: the simplicity of Markdown and the power of React.

Happy blogging! 🚀

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# Choosing a Stack for a Portfolio Site

Canonical URL: https://www.harjotrana.com/blog/building-portfolio-website
Author: Harjot Singh Rana
Published: 2025-09-11
Reading time: 3 min

> The tools worth reaching for when the site itself is the work sample, and the ones that aren't.

Creating a portfolio website that showcases your skills and projects is essential for any developer. In this post, we'll explore the modern tools and technologies that make building impressive portfolio sites easier than ever.

## The Modern Tech Stack

### Frontend Framework: Next.js
Next.js has become the go-to framework for modern web development. It offers:
- **Static Site Generation**: Blazing fast load times
- **Server-Side Rendering**: Dynamic content with SEO benefits
- **API Routes**: Backend functionality without separate servers
- **Image Optimization**: Automatic image optimization and resizing

### Styling: Tailwind CSS
Tailwind CSS revolutionizes the way we write styles:
- **Utility-First Classes**: Build custom designs without writing CSS
- **Responsive Design**: Mobile-first approach made simple
- **Dark Mode**: Built-in support for multiple themes
- **Customization**: Highly configurable through config files

### Animation: Framer Motion
Adding smooth animations and interactions:
- **Gestures**: Drag, pan, hover, and tap animations
- **Layout Animations**: Smooth transitions between layouts
- **Scroll Animations**: Animate elements as they come into view

## Essential Components

### Header Navigation
A responsive navigation menu that works across all devices:
- Desktop menu with smooth hover effects
- Mobile hamburger menu with slide-in animation
- Active state indicators for current page

### Project Showcase
Display your projects effectively:
- Grid layout with responsive columns
- Hover effects revealing project details
- Modal popups for detailed project views
- Links to live demos and source code

### Skills Section
Present your technical skills visually:
- Progress bars showing proficiency levels
- Icon-based representation of technologies
- Interactive filtering by category
- Animated entrance effects

## Performance Optimization

### Image Optimization
- Next.js Image component for automatic optimization
- Lazy loading for better initial load times
- WebP format support with fallbacks
- Responsive images serving different sizes

### Code Splitting
- Automatic route-based code splitting
- Dynamic imports for heavy components
- Preloading critical resources
- Bundle analysis tools

## SEO Best Practices

### Meta Tags
Dynamic meta tags for each page:
- Open Graph tags for social sharing
- Twitter Card support
- Structured data for rich snippets
- Dynamic sitemap generation

### Performance Metrics
- Core Web Vitals optimization
- Lighthouse score improvements
- Progressive Web App features
- Service worker for offline support

## Deployment and Hosting

### Vercel Integration
Seamless deployment with automatic optimizations:
- Automatic HTTPS setup
- CDN distribution globally
- Serverless functions for API routes
- Automatic deployments on git push

### Analytics Integration
Track user engagement:
- Vercel Analytics for performance metrics
- Custom event tracking
- Heatmaps and user session recording
- Conversion goal tracking

## Future Enhancements

### Blog Functionality
Adding a blog section for content marketing:
- MDX support for rich content
- RSS feed generation
- Comment system integration
- Search functionality

### Advanced Features
Plans for future improvements:
- Multi-language support
- Dark mode toggle
- Interactive timeline
- Email newsletter signup

## Conclusion

Building a modern portfolio website requires careful planning and the right tools. By leveraging Next.js, Tailwind CSS, and other modern technologies, we can create fast, beautiful, and maintainable websites that effectively showcase our work.

The key is to balance functionality with performance, ensuring that every feature serves a purpose while maintaining excellent user experience.

Happy coding! 🚀

More writing: https://www.harjotrana.com/blog

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers

---

# MVP Scope Estimator

Canonical URL: https://www.harjotrana.com/tools/mvp-scope-estimator

> Turn a rough product idea into a clearer first milestone. Nine quick choices, an explainable scope read, and a brief you can keep.

This is an interactive, deterministic tool; the nine questions and the resulting brief are rendered in the browser at the canonical URL. No AI, no account, and nothing leaves the browser.

## What this helps you decide

A useful first release has a defined user, a central workflow, and a launch bar that matches what needs to be true now—not everything the product might become.

The estimator makes the main complexity drivers visible: roles, integrations, AI behavior, runtime needs, launch requirements, and data sensitivity.

It will not invent a price, promise a timeline, or make a technical recommendation without the real context. Those are conversation-level decisions.

## Questions and answers

### Does this use AI?

No. The estimator uses a small deterministic ruleset, so every result follows from the choices you make. That keeps the result explainable, fast, and independent of an API key.

### Is this a quote or delivery estimate?

No. It is a scoping aid. It does not promise price, timing, delivery capacity, architecture, or a final proposal. Those decisions need the actual product context and a shared discussion.

### Is my product information stored?

Your selections and the copyable brief stay in the browser. The site records only non-identifying aggregate tool events such as completion, scope band, and safe complexity flags; it does not send your selected answers or product details to analytics.

### What should I do with the result?

Copy the brief into a project inquiry, a planning document, or a conversation with your team. Use it to make the first milestone and the work you are deliberately deferring explicit.

Ready to talk it through? https://www.harjotrana.com/hire

---

Site guide for agents: https://www.harjotrana.com/llms.txt · Full site as Markdown: https://www.harjotrana.com/llms-full.txt · Sitemap: https://www.harjotrana.com/sitemap.xml · Developer resources: https://www.harjotrana.com/developers
