Imported from yamcodes/arkenv (
AGENTS.md). Install upstream withnpx skills add yamcodes/arkenv. Copyright stays with the author.
AGENTS.md
Cursor Cloud specific instructions
ArkEnv is a Nub + Turborepo monorepo for a TypeScript env-var validation library. Nub is the package manager (packageManager: nub@…, lockfile nub.lock, isolated .store via nub.jsonc) as well as the script runner — do not mix pnpm install into the same tree. There are no databases or external services; the product long-running process is the www docs site (Next.js). apps/dash is an optional maintainer dashboard and is not started by nub run dev / nub run www. Standard commands live in package.json, docs/CONTRIBUTING.md, and docs/TESTING.md — prefer those.
This is the
v1branch. Its package layout differs fromdev(v0): herepackages/arkenvis the CLI (published asarkenv) and the core runtime lives inpackages/core(published as@arkenv/core). There is nopackages/clionv1.v1publishes pre-release versions (1.0.0-alpha.x) under thealphanpm tag.
Services / apps
packages/*— the publishable library packages:arkenv(the CLI, inpackages/arkenv/),@arkenv/core(core runtime, inpackages/core/),@arkenv/standard(inpackages/standard/), plus@arkenv/nextjs,@arkenv/nuxt,@arkenv/vite-plugin,@arkenv/bun-plugin,@arkenv/build,@arkenv/agent-plugin, and@arkenv/fumadocs-ui(which live in thenextjs/,nuxt/,vite-plugin/,bun-plugin/,build/,agent-plugin/, andfumadocs-ui/directories), plus internal helpers underpackages/internal/*. These are the core product.apps/www— the documentation website (Next.js 16 + Fumadocs). This is the product app.apps/dash— optional maintainer Dashfy dashboard (GitHub + npm). Not in CI. Run withnub run dash(Vite on http://localhost:3001, Dashfy server on http://127.0.0.1:5001). Copyapps/dash/.env.exampletoapps/dash/.envfirst.apps/playwright-www— Playwright e2e suite targetingwww.apps/playgrounds/*andexamples/*— framework sandboxes / fixtures (optional).
Common commands (run from repo root)
Prefer Nub (nub run, nubx, nub install, nub / nub watch, nub node). It replaces tsx / ts-node / tsconfig-paths / dotenv, pnpm run, npx / pnpm exec, pnpm install, and nvm.
- Install deps:
nub install - Build everything:
nub run build· packages only:nub run build:packages - Run docs site (dev):
nub run www(serves onhttp://localhost:3000) - Maintainer dashboard:
nub run dash(Vite onhttp://localhost:3001; copyapps/dash/.env.exampletoapps/dash/.envfirst) - Lint/format + workspace validation:
nub run check - Typecheck:
nub run typecheck - Unit/integration tests:
nub run test -- --run(Vitest) - E2E: see caveat below.
Non-obvious caveats
- Changesets /
@manypkgdo not understand Nub identity.scripts/ensure-manypkg-workspace.jsmaterializes a gitignored packages-onlypnpm-workspace.yamlfrompackage.json→workspaces.packages(postinstall,nub run check/changeset/release, and the Release workflow) so Changesets can discover packages. Do not commit that file or runpnpm installbecause of it. Release publish does not use pnpm:scripts/changeset-publish.jsrewritesworkspace:/catalog:to concrete versions, forces Changesets onto the npm publish tool, then runschangeset publish(avoids Nub’s broken lockfile-lesspnpm@lateststub). - Pending changesets are only
.changeset/<name>.md. Never write new ones under.changeset/pre/— that directory is the consumed archive filled bychangeset version/ Version Packages whilev1is in pre mode. Files that only exist inpre/are skipped by release planning and will not version (when nothing else is pending,changesets/actionlogs “No changesets found”). See the changeset skill and.agents/AGENTS.md§6. - Bun is required for
@arkenv/bun-pluginand some bun playground/example flows, and CI installs Bun 1.3.13. It is installed globally at~/.bun/bin(onPATHvia~/.bashrcfor login shells); if a session's shell can't findbun, runexport PATH="$HOME/.bun/bin:$PATH". Bun is not needed for the core build /nub run test/ runningwww. - Node 24.21.0 is pinned in
.node-version(exact patch, committed). CI default jobs read that file; do not hardcode 24 in workflows. Latest-channel jobs passnode-version: 26so they rewrite the pin for that job only (nub runstill follows.node-version; see nubjs/setup-nub#12). Playground/exampleengines.nodeis>=22, except SolidStart (>=24). tsdown warns on Node < 22.18. Prefernub nodeovernvm. Bareni/nstill use PATH Node if it is not the Nub shim. - E2E must be run the CI way (against a production server), not against
nub run www. The Playwright config usesnext startwhenCIis set andnext devotherwise; the dev server emits console errors that the smoke test forbids and can be overwhelmed by parallel workers (ERR_CONNECTION_REFUSED). Buildwwwfirst (nub run build --filter=www...), then run e.g.CI=1 nubx playwright test --project=chromiumfromapps/playwright-www. Playwright browsers must be installed once per VM:nubx playwright install --with-deps chromium firefox(webkit is macOS-only). - The CLI tests print
fatal: not a git repository/Using 'master' as the name...git hints while running — this is expected (they scaffold temp git repos) and does not indicate failure. - To run a package that consumes the local (workspace) build, use a workspace playground (e.g.
apps/playgrounds/node, which depends on@arkenv/core: workspace:*). Copy.env.exampleto.envfirst, thennub run start(scripts use Nub to run TypeScript and load.env). Theexamples/*projects are standalone npm projects that resolve the published packages (e.g.@arkenv/core) from npm, not the local build.
Learned User Preferences
- Spell public copy as "Typesafe" (one word), not "Type-safe".
- Use the full phrase "environment variables" in headings and SEO-facing copy; "env vars" is fine in subheads and body.
- Match docs voice to turborepo.dev plus the existing getting-started and root docs (index, support policy, community).
- Match homepage hero typography to turborepo.dev (heading size, weight, and text style).
- Nub (https://nubjs.com/) is a real Node toolkit the user likes; never treat "Nub" as a typo for Bun. Prefer it over
tsx/ts-node/tsconfig-paths/dotenv,pnpm run,npx/pnpm exec,pnpm install, andnvmin this repo (scripts, CI, CONTRIBUTING, AGENTS) and inexamples//apps/playgrounds/. User-facing docs keep stock defaults selected (npx/node --env-file/tsx) and authorpackage-installfences in npm form; the tabs also offer Nub (nubx/nub add). Do not replace npm-form fences with Nub-only copy except on the dedicated Using ArkEnv with Nub guide. - Do not claim "zero dependencies" without "runtime" unless talking only about core/standard.
Learned Workspace Facts
- Site Nav is a full-bleed wireframe header (row 0 of the hairline grid): no floating pill, no side inset,
border-bottomin--color-rule. Get started / search / nav-link hover / search dialog overlay (.arkenv-search-dialog) use0.375remradius (--radius-control/--site-nav-radius), notrounded-full. Nav labels are medium, not semibold: wordmark / Get started / Searchfont-weight: 500; Docs / Playground / Roadmap450; mobile menu links stay400, drawer Get started500. Hero install command and code windows use the same0.375rem(--radius-frameon editor chrome). The announcement chip is the exception: fully round (--radius-pill). Docs share that same Row 0. Docs are not a bento: Vite four-rail cage in--color-rule-y(--color-fd-borderaliases it; slightly softer than horizontal--color-rule) —#nd-sidebarand#nd-toceach haveborder-inline-startandborder-inline-end. Inner splitters (sidebar end, TOC start) stop at the site footer; outer rails (sidebar start, TOC end) continue through.site-footer-bleedvia.docs-outer-rails— a centeredmin(100%, var(--fd-layout-width))column with the sameborder-inline-start/endas the cage (notbackground-position%, which sits ~1px left of the borders) so they cross the edge-to-edge footer-top hairline down to the bottom of the page. The copyright bar hairline (.home-aurora__footer-meta) is edge-to-edge like the homepage (100cqi/50% - 50cqi). Docs and home shareSiteFooter(docs passesrailsso the bleed + outer cage wrap the same footer). MIT License →https://github.com/yamcodes/arkenv/blob/v1/LICENSE; Yam Borodetsky →https://yam.codes. Copyright bar uses homepagepadding-block(--sf-space-sm); no extra pad below the meta. The TOC spy is a 2px ink bar atinset-inline-start: -1pxon the article↔TOC splitter (no inner TOC line). The Give feedback hairline (data-docs-toc-footer) spans rail-to-rail;--docs-toc-gutterpads the title/list/footer, not the column. The article pager hairline ([data-docs-footer]border-top) spans the sidebar inner rail to the TOC inner rail;--docs-page-gutterpads the Previous/Next links. Sidebar active-pill left edge lines up with the Site Nav wordmark icon (cyan helm); equal--site-nav-guttercolumn pads inset the pill the same from both sidebar rails; items usepx-2.5inner pad so glyphs clear the 0.25rem radius. The outer sidebar rail stays. Sidebar active pills and the “Enjoying ArkEnv?” card use0.25remradius; Copy page uses0.375rem(--docs-control-radius), same as Get started / Search. Docs have no hero spotlight or 24px dot field —#docs-chrome-shelland the mobile drawer are paper (the homepage hero keeps its atmosphere).#nd-pageis solid paper so the reading column stays open. - Homepage TypeScript snippets (hero, 04 Modular ArkType / Zod / Valibot) use the same Twoslash highlighter as the docs (
highlightTwoslash+HeroTwoslashHtml). The 01 IDE autocomplete mock, 02 dark terminal dump, and 03 browser overlay are not Twoslash. Pitch sections have no numbered kickers and no zig-zag. Sections 1–4 sit in a Vite-style seamless bento: 2×2 for the four mechanics. Copy packs from the top; widgets hug their content andmargin-top: autoso they bleed to the cell’s bottom hairline (no cell bottom pad, square window corners, no window bottom border). Leftover cell height is paper above the window, not empty dark inside it. At ≥64rem the 2×2 is two flex columns — do not subgrid heading/widget tracks. 2×2 body copy is 70% of the half-cell (not a 26rem cap). Hairline grid uses--color-rule(horizontals) and--color-rule-y(long vertical rails, slightly softer) with no gaps; both are dimmer than the old undifferentiatedoklch(30% 0.02 200)rule;--rule-hairstays 1px; short interior verticals (trust-bar inset, install-pill divider) use--color-ruleso they read on paper-2; vertical rails stay on the content column while horizontals, hatch stripes, paper bands (trust + footer), and the footer-top rule run edge-to-edge (open-slide). Hero, trust, footer, and 2×2 copy share one content inset (--home-rail-inset+--home-gutter); pitch cells pad inline with--home-gutter, not--space-lg. Grid intersections are bare crossings — no carets, notches, plus ticks, or ArkEnv marks. Headings: “Fail-fast at startup” · “Strictly typed” · “Full-stack ready” · “Bring your own validator”. Then outro. Nav Playground is the StackBlitz example (external), not an in-page#demo. Section 1 is a dark terminal (home-aurora__tty) docked to the cell bottom — a normal contained window (not a clipped peek):$ npm run dev(cyan$matching the hero install pill via--color-accent), blank line, then the realArkEnvErrordump (Errors found while validating environment variablesin--color-danger/ ANSI 31;ArkEnvError:stays unstyled ink-2). Issue lines indent 2 spaces: keys in--color-cmd(ANSI 33 yellow),(was …)values in--color-accent(ANSI 36 cyan) —DATABASE_URL must be a URL string (was [REDACTED])andPORT must be a number (was a string). NoInvalid environment variables, novalidating environment...filler, noinfo - loaded env from .env(ArkEnv is not a dotenv loader), no>_, noready/server-started line, no stack, no Node internals, no Copy. Section 2 is a dark VS Code mock (import { env } from "./env";/const db = env.|/const port = env.PORT;) docked to the cell bottom; editor body top pad is--space-2xsso the snippet sits on the chrome, not in a well. IntelliSense (DATABASE_URLas a string) hangs off the caret onenv.|over the followingconst portline inside min pane pad (padding-block-end: 6.5rem) — inside the code window, not clipped at the cell hairline. The editorprestaysoverflow: visiblesooverflow-x: autocannot compute asoverflow-y: autoand clip the popup. Not a light editor. No dummy blank lines to equalize card heights. No before/after./env.tswall — that tutorial belongs in docs under “Why ArkEnv?”. Section 3 is a browser window (home-aurora__browser: traffic lights +localhost:3000omnibox) with a Next.js Runtime Error overlay (home-aurora__fail): redRuntime Errorbadge, the messageDo not access server-only key 'DATABASE_URL' on the client since it will leak sensitive data (prevented by ArkEnv), a grey Next.js source bar (TS app/components/header.tsx (5:12) @ Header), and a one-line code-frame of the leak (env.DATABASE_URL). No Call Stack, no schema behind it, no Copy (the overlay is not a snippet). Chrome + overlay hug (grid-template-rows: auto auto, no inner1fr/ flex-grow spacer). Leftover cell height is paper above the window (margin-block-start: autoon the visual), not empty dark inside it. Section 4 is one mixed./env.tsvia@arkenv/core, a normal full-width code window like the hero example and Strictly typed (not a clipped/peek crop; no bentooverflow-x: clip/overflow-wrap: anywhereon the snippet): visible snippet starts atexport const env(imports sit above twoslash---cut---). ArkType DSL (NODE_ENV), Valibotv.pipe(v.string(), v.url())(DATABASE_URL—v.url()is an action, not a schema), Zodz.boolean()(DEBUG) — notype()wrap, noz.coerce, noLOG_LEVEL/PORT, no validator tabs (the hero example is the only tab bar). Homepage tabs are hero-only (ArkType / Zod) on the sliding-ink tablist. Code windows sharehome-aurora__code-window(filename chrome uses the same--color-paper-2as the snippet body — no two-tone header) and the docsCodeBlockCopyButton(accessible name “Copy”) on the filename chrome — hero copies the visible ArkType/Zod tab; pitch code windows (Strictly typed, Bring your own validator) copy their raw source. The fail-fast terminal is a TTY dump without Copy. The Full-stack ready overlay has no Copy. Inline links match the footer: ink-2, hover to ink, no underline. Section 3 leak highlight useshome-aurora__fail; section 1’s terminal ishome-aurora__tty(real dump, not an overlay chip). Hero example tabs are ArkType / Zod only; Modular is one mixed snippet (starts atexport const env; ArkType DSLNODE_ENV, Valibot URL pipeDATABASE_URL, Zod booleanDEBUG— notype(), noz.coerce, no numeric Valibot). "Typesafe environment variables" / "with [cycling name]" on one line (ArkType, Zod, Valibot) — glued with a non-breaking space so “with” and the name stay on one line without jump or overflow. That cycle is independent of the example tabs (ArkType / Zod only) and pauses only while the pointer is over the cycling name (.home-aurora__cycle), not the rest of the headline or the example. Dwell is 3s; the name slide is 375mscubic-bezier(0.16, 1, 0.3, 1). Reduced motion locks to ArkType. The example window is vanilla./env.tsonly — no Vanilla/Vite/Next.js switcher. Hero atmosphere is a 2.5% white spotlight plus the dot field — no teal radial blooms. The 2×2 bento sits on the same flat--color-paperas the hero (not dark-gray cards); trust and footer stay paper-2 bands. The hero trust bar is one band (Colin’s quote + “Works with” marquee). At ≥64rem a short inset hairline sits in the gap between them —--home-gutterfrom “Creator of Zod” and the same--home-gutterbefore “Works with” (matching avatar↔left rail). Paint it--color-rule(not--color-rule-y) so it reads on the paper-2 band. It must not meet the band’s top/bottom rules. Open-slide-style hatch stripes break the bento between grid→outro (not after the trust bar — too soon). Both hero tabs use the same keys (DATABASE_URL,PORT,CI); ArkType PORT is"0 <= number.integer <= 65535 = 3000"(notnumber.port) and CI is"boolean = false"so switching tabs is a git-diff of the same rules. Hero install and outro install: command pill and copy actions consumeRELEASE_CONFIG.initCommandfromapps/www/lib/config/release.ts(bare$ npx arkenv initwhile productlatestpoints at RC;INSTALL_TAGis empty independently of the RC channel badge). A sub-row under it is “Copy prompt / View repo” (hero) or “Copy prompt / Read the docs” (outro). Channel badge usesRELEASE_TAG; install CTAs useINSTALL_TAG. Outro is a full-width bento span (“Start validating with ArkEnv” + the same command pill + “Copy prompt / Read the docs”), not a rounded inset card. Outro heading is heading-32 (--text-heading32px / 40px / 450), not a display size. No outro subline. No Modular validator tabs — the hero example window is the only tab bar. - Homepage hero subhead: "Get a strictly typed
envobject using your existing TypeScript validator. No boilerplate. Zero runtime dependencies." - Site tagline (footer and similar surfaces): "Typesafe environment variables with ArkType, Zod, or Valibot."
- Homepage
<title>is punchy: "ArkEnv - Typesafe environment variables for TypeScript" (ASCII hyphen, not an en-dash). Docs pages use[Page] | ArkEnv(pipe, no "Docs"). Library names (ArkType, Zod, Valibot) go in<meta name="description">, not the title. Middle dots (·) are for on-page text only. - "Zero runtime dependencies" is true of
@arkenv/core(peer arktype only) and@arkenv/standard(none). It is not true of the CLI or framework plugins (they depend on@arkenv/build,jiti,chokidar, etc.). - Docs use package-install tabs and stock Node defaults for user-facing runner / install copy (
npx,node --env-file, framework loaders). Nub appears as an extra package-manager tab (nubx/nub add) generated from the npm fence; it is not the default selected tab. Dedicated Nub runner guidance lives in Using ArkEnv with Nub. - turborepo.dev is the visual and docs-voice reference; clone its docs into a gitignored folder when needed.
