원클릭으로
nn
@nextnode-solutions/nn — local dev DX CLI. Auto-load when working on nn package or any project that uses nn.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
@nextnode-solutions/nn — local dev DX CLI. Auto-load when working on nn package or any project that uses nn.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Deep interview for a single feature or module brief that produces a final spec or implementation plan.
@nextnode-solutions/logger standards. Reference this skill when a project uses @nextnode-solutions/logger or when logger integration is being added.
Manage n8n workflows on the NextNode automation instance with the local n8n-cli tool.
NextNode brand guidelines — color palette, typography, logo system, and per-project branding rules. Use when doing UI/frontend work on a NextNode project.
NextNode ecosystem hub. Auto-load when working on any NextNode or SaaS project — covers nextnode.toml config, CI workflows, docker-compose rules, and cross-references to package skills.
Audit a NextNode/SaaS project against all NextNode standards — produces a compliance report with pass/fail/missing status for every required item.
| name | nn |
| description | @nextnode-solutions/nn — local dev DX CLI. Auto-load when working on nn package or any project that uses nn. |
| version | 1.2.1 |
| user-invocable | false |
| autoload-dirs | ["/Users/walid/Development/nextnode","/Users/walid/Development/saas"] |
Published as @nextnode-solutions/nn (v1.2.1). Built with citty, uses @nextnode/cli as workspace dependency for shared types and config.
Package: packages/nn/ in the infrastructure monorepo.
| Command | Purpose | Key Args |
|---|---|---|
nn up | Start local dev (Docker services + app dev server) | --containerized, --reset-ports |
nn down | Stop local dev (kills dev server + Docker services) | --clean (removes volumes) |
nn env show | Display resolved env vars | <env> (default: local), --reveal, --diff <env> |
nn env check | Validate env vars against requirements | <env> (default: local) |
nn env init | Create .env.local, .env.dev, .env.prod templates | — |
nn env push | Push env vars to GitHub environment secrets | <env> (required: dev, prod), --file |
nn up Flownextnode.toml (detectServices()).nn/ports.json, reusable across restarts).nn/ directory + .gitignore entries.env.local → userVars (empty if file missing)[secrets] vars → write to .env.local (add-only, 0o600 perms).nn/.env)buildLocalEnvVars()) — computed always wins over user{ ...userVars, ...computedVars } → mergedVars (warns on overrides)[auto]/[secret]/[user] labelsdocker-compose.local.yml in .nn/ (if services fresh)detectDevCommand())process.env (lowest)
<- .env.local vars (user-provided + persisted [secrets])
<- computed service vars from buildLocalEnvVars() + resolveDockerEnv() (highest)
[auto]: Service-derived vars computed by nn up (SUPABASE_URL, DATABASE_URL, REDIS_URL, etc.)[secret]: Config-declared secrets generated by nn up via [secrets] in nextnode.toml[user]: User-provided vars from .env.localKeys are generated/reused in resolveDockerEnv(), written to .nn/.env, then passed to buildLocalEnvVars() so both Docker and app use the same values. Single owner — no split brain.
nn env Subcommandsnn env init.env.local, .env.dev, .env.prod templates (skips existing files).env.local: excludes auto-generated vars (only user-provided + placeholder for secrets).env.dev / .env.prod: includes ALL vars, generates unique random [secrets] values per env.gitignore includes .env.* patternsnn env checkSERVICE_REQUIRED_VARS, [secrets] configgetAutoGeneratedVars()) from required list<...>, empty, whitespace-only) as MISSINGnn env push.env.{env} to GitHub environment secretslocal (error exit)gh secret set --env <ghEnvName> per selected varnn env show--reveal: show actual values--diff <env>: compare two environments (+ only in first, - only in second, ~ different values)packages/nn/src/lib/)| Library | Key Exports |
|---|---|
| config | requireConfig() — loads nextnode.toml via @nextnode/cli, bundles nextnode.default.toml |
| services | detectServices(config): DetectedServices, getAutoGeneratedVars(config): ReadonlySet<string> — returns max set of auto-generated var names for configured services |
| ports | resolvePortMap(services, appPort, nnDir, opts?), isPortAvailable(port), findAvailablePort(start, max), DEFAULT_PORTS, PortMap |
| compose | generateLocalCompose(config, services, ports), writeLocalComposeFiles(projectDir, config, composeContent) — writes compose + Supabase support files (kong.yml, roles.sql, init-db.sh, kong-entrypoint.sh) to .nn/ |
| docker | checkDockerAvailable(), startDockerServices(composePath, envFilePath?), stopDockerServices(composePath, opts?), areServicesRunning(composePath), getServiceStatuses(composePath) |
| env | readEnvFile(env, cwd, filePath?), parseEnvFile(content), writeEnvLocal(projectDir, vars): EnvVars, buildLocalEnvVars(services, ports, supabaseKeys?, pgPassword?), isSensitive(name), maskValue(value), formatValuePreview(value, name, reveal), isPlaceholderValue(value), resolveEnvFile(env, filePath?), mapGitHubEnvName(env), SERVICE_REQUIRED_VARS, EnvVars (re-export) |
| oauth | computeLocalOAuthEnvVars(oauthConfig, apiPort, localVars: Readonly<EnvVars>) — pure function, receives pre-parsed vars instead of reading .env.local. Falls back to process.env for credentials. Warns and skips providers with missing credentials. |
| secret-gen | generateSecret(decl: SecretDeclaration): string — hex via randomBytes, base64url via randomBytes, uuid via randomUUID. Exhaustive default: never on discriminant. |
| dev-server | detectDevCommand(projectDir) — checks package.json scripts.dev, then framework-specific defaults (astro, next, vite) |
| logs | createPrefixWriter(serviceName, colorIndex), streamDockerLogs(composePath), pipeWithPrefix(proc, serviceName, colorIndex) |
| process | gracefulKill(proc, timeoutMs), waitForHealthy(checkFn, label, maxMs), writePidFile(nnDir, pid), readPidFile(nnDir), removePidFile(nnDir), killProcess(pid), isProcessRunning(pid) |
type ServiceName = 'supabase' | 'redis'
interface DetectedServices {
names: ServiceName[]
hasSupabase: boolean
hasRedis: boolean
}
interface PortMap {
supabaseApi?: number // default 54321
supabaseStudio?: number // default 54323
supabaseDb?: number // default 54322
supabaseAuth?: number // default 9999
redis?: number // default 6379
app: number // default 4321
}
SecretDeclaration, GenerateAlgorithm, EnvVars, and ProjectConfig are defined in @nextnode/cli types — see nextnode-infra skill.
| File/Dir | Purpose | Written by |
|---|---|---|
.env.local | User secrets + generated [secrets] | User + nn up (secrets only, add-only) |
.nn/.env | Infra vars for Docker Compose | nn up (every run) |
.nn/ports.json | Persisted port assignments | nn up |
.nn/docker-compose.local.yml | Local Docker Compose | nn up |
.nn/supabase/ | Kong config, init scripts, roles.sql | nn up |
.nn/app.pid | Dev server PID | nn up |
.env.dev | Deploy vars for dev | User + nn env init |
.env.prod | Deploy vars for prod | User + nn env init |
All secret-containing files (.env.local, .nn/.env) enforced 0o600 via chmodSync after every write.
[secrets] Config (nextnode.toml)[secrets]
TOKEN_ENCRYPTION_KEY = { generate = "hex", bytes = 32 }
SESSION_SECRET = { generate = "base64url", bytes = 32 }
INTERNAL_API_KEY = { generate = "uuid" }
hex, base64url (both require bytes >= 16), uuidnn up generates missing secrets → writes to .env.local (add-only, never overwrites).env.localpackages/cli/src/lib/config.ts (validateConfig())packages/nn/src/lib/secret-gen.ts.env.local templates and local checks)getAutoGeneratedVars(config) returns the maximum possible set:
| Service | Vars |
|---|---|
| Supabase | SUPABASE_URL, DATABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, JWT_SECRET, POSTGRES_PASSWORD |
| Redis | REDIS_URL |
SERVICE_REQUIRED_VARS| Service | Required in deploy envs |
|---|---|
| supabase | JWT_SECRET, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, POSTGRES_PASSWORD |
| redis | REDIS_URL |
| r2 | R2_BUCKET_NAME, R2_ACCOUNT_ID, R2_ENDPOINT_URL |
.nn/ports.json reused across restarts. --reset-ports to reassign.localVars (pre-parsed .env.local) with process.env fallback. GitHub OAuth uses GH_* prefix (not GITHUB_*).isSensitive() checks suffixes (_KEY, _SECRET, _PASSWORD, _TOKEN, _CREDENTIALS, _API_KEY), prefixes (JWT_), exact matches (DATABASE_URL, REDIS_URL).isPlaceholderValue() returns true for <...>, empty, whitespace-only values..nn/ cleanup: writeLocalComposeFiles() cleans up stale directory artifacts (Docker creates bind-mount paths as directories when source files don't exist).