Contributing to Nodaro

Thanks for your interest in contributing. Nodaro is a source-available AI workflow editor — under the Nodaro Sustainable Use License, anyone can run their own copy for personal use, for development and evaluation, or for internal-business use in companies of up to three people, build apps on top of the REST API, or send patches upstream. This document is the entry point for the third group: people who want to land code in this repo.

If you instead want to use Nodaro on your own server, start at the Community Edition Quickstart. For building integrations against the API, see API Integration and the SDK Quickstart.

1. Project layout

The repo is a single npm workspaces monorepo. Top-level folders:

Path What lives here
backend/ Fastify HTTP API, BullMQ workers, orchestrator. Node 22, TypeScript.
frontend/ Vite SPA — visual editor + presentation/app-runner + admin panel. React 19, React Router 7, React Flow.
packages/shared/ Pure logic shared across the stack: types, model registries, prompt builders. Publishes as @nodaro/shared.
packages/client/ Typed REST SDK with three auth modes and 17 resource classes. Publishes as @nodaro/sdk.
packages/remotion/ Remotion video compositions (slideshow, scene-graph, motion-graphics, etc.).
supabase/migrations/ Database schema as forward-only SQL migrations.
docs/ Public docs — this folder is published to GitHub Pages.
docs/design/ Curated design notes (RFD-style) — the why behind key features.
scripts/ Repo-level utilities (architecture graph generator, audits, etc.).
.changeset/ Pending version-bump intents for @nodaro/shared + @nodaro/sdk.

Some source-code comments reference design docs by a specs/… path. Those design docs live in Nodaro’s private planning repo; a curated public subset is published under docs/design/.

The root CLAUDE.md is the canonical “house rules” file — coding standards, the Provider Enum Sync checklist, and the New Node Registration checklist all live there. Always read it before a non-trivial PR.

2. Dev environment setup

Prerequisites

node --version    # 22 or newer
npm --version     # ships with Node

Install

git clone https://github.com/nodaroai/app.nodaro.ai
cd app.nodaro.ai

# Single command — installs every workspace
npm install

Configure secrets

cp .env.example .env
# Edit .env — at minimum:
#   SUPABASE_URL
#   SUPABASE_SERVICE_ROLE_KEY
#   SUPABASE_ANON_KEY
#   INTERNAL_ORCHESTRATOR_SECRET (any 32-byte hex)
#   At least one AI provider key (KIE_API_KEY / REPLICATE_API_TOKEN / ANTHROPIC_API_KEY)

Generate the orchestrator secret in one line:

echo "INTERNAL_ORCHESTRATOR_SECRET=$(openssl rand -hex 32)" >> .env
echo "SOCIAL_ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env

The full list of supported variables is in .env.example. The Community Edition Quickstart has the full setup flow including Supabase project creation.

Build the shared package

The frontend resolves @nodaro/shared from the package’s dist/ output, so you have to build it once before starting the dev server. After that, tsup --watch (run via the package’s own dev script) keeps it up to date if you edit shared code.

npm -w @nodaro/shared run build

Run the dev servers

In two terminals:

# Terminal 1 — backend on :9000
cd backend
npm run dev

# Terminal 2 — frontend on :3000 (proxies /v1/* to :9000)
cd frontend
npm run dev

For Supabase: the easiest path is creating a free project on supabase.com and pointing your .env at it. A fully local stack is possible via the Supabase CLI (supabase start) but is more involved — see the Supabase docs.

3. Coding standards

The canonical reference is CLAUDE.md at the repo root. The most frequently cited rules:

4. Branching and PRs

When opening a PR:

5. Testing

Each workspace ships its own Vitest suite. The full suite runs from the repo root:

npm test                       # runs every workspace's "test" script

Or per workspace:

npm -w @nodaro/shared test     # pure-logic unit tests
npm -w @nodaro/sdk test     # SDK contract tests against MSW
cd backend && npm test          # route + service tests
cd frontend && npm test         # component + hook tests

What to test:

CI runs tsc --noEmit, vitest, and a small linting pass. Local flakiness is rare; if a test fails locally but passes in CI (or vice versa), open an issue with reproduction steps.

ffmpeg output characterization (opt-in suite)

Ordinary unit tests assert the argument strings passed to ffmpeg; they cannot see a change in what ffmpeg actually renders (filter behavior differs between ffmpeg versions — that is why the Dockerfile pins the exact ffmpeg package). The characterization suite in backend/src/providers/video/__characterization__/ closes that gap: it renders synthetic fixtures through every ffmpeg-backed operation and compares measured properties of the decoded output (energy, band spectrum, decay envelope, duration, per-frame luma) against committed golden values.

It is deliberately excluded from npm test — its numbers are only valid against the exact pinned production ffmpeg, not whatever binary your laptop or a bare CI runner happens to have. Run it inside the production image environment instead:

backend/scripts/characterize-in-image.sh check   # compare against golden
backend/scripts/characterize-in-image.sh bless   # rewrite golden (deliberate!)
cd backend && npm run characterize:report -- --against ffmpeg-X.json

If you touch anything under backend/src/providers/video/, run the check before opening the PR (CI runs it too, inside a container with the pinned ffmpeg). If your change is supposed to alter rendered output, re-bless inside the image and commit the golden diff with an explanation of every metric that moved. Never edit golden values by hand, and never bless against an unpinned/local ffmpeg — the suite’s version guard will reject it.

6. Adding a new node

Adding a new node type touches a lot of files — backend route, frontend component, executor wiring, registries — and skipping any one of them produces a confusing failure mode (the node doesn’t appear in the sidebar, or the Run button silently does nothing, or the orchestrator can’t resolve inputs). The canonical 19-step checklist is in CLAUDE.md under “New Node Registration”.

The high-level shape of a new node:

Two unintuitive details that bite contributors:

There’s a more detailed walkthrough in the New Node Registration checklist in the root CLAUDE.md. The 19-step checklist in CLAUDE.md is the version that actually stays in sync.

7. Adding a new AI provider

Adding a provider to an existing node type (e.g. a new image-gen model) is not the same as adding a new node. The checklist is shorter — 12 files — and lives in CLAUDE.md under “Provider Enum Sync”. The headline gotchas:

For node types that use provider-aware dropdowns (resolution, quality, voice), make sure the config panel includes the fail-safe useEffect([currentProvider]) from step 12b — this snaps stale data values to the first valid option when the user switches providers, otherwise persisted workflows trip the route’s Zod enum.

8. Releasing the SDK packages

Three packages in packages/ publish to npm: @nodaro/shared, @nodaro/sdk, and @nodaro/cli. The release flow is Changesets, fully automated by .github/workflows/release.yml:

# After making a change that affects a published package
npx changeset

The CLI prompts you to pick which packages changed, the bump type (patch / minor / major), and a one-line summary. It writes a markdown file under .changeset/<random>.md — commit this with your PR. CI enforces this (the Changeset Guard job fails PRs that change published packages without one); for changes that genuinely need no release note, use npx changeset --empty.

From there everything is automatic:

  1. When your PR merges to dev, the release workflow maintains a rolling “Version Packages” PR that applies all pending changesets (version bumps + CHANGELOGs).
  2. When that PR merges and dev is promoted to main, the workflow publishes any unpublished versions to npm (via npm Trusted Publishing / OIDC — no tokens), tags the release, creates GitHub Releases, and rebuilds the standalone CLI binaries when @nodaro/cli was part of the batch.

You don’t need to publish anything yourself — the changeset is the only thing the contribution flow asks for. Workspaces under ignore in .changeset/config.json (backend, frontend, remotion) never publish and need no changesets.

9. Code of conduct

Be kind. Be respectful. Assume good faith. We follow the Contributor Covenant 2.1 — read it once if you haven’t. Harassment of any kind is grounds for removal from the project.

In practice this means:

10. Where to ask questions

11. License

Nodaro is fair-code with three license tiers (see LICENSE.md for the full overview):

Placement rule for contributions: every published version of the Apache packages is an irrevocable grant, so new prompt engineering, catalogs, doctrine, and presets belong in packages/prompts/ (FSL) or backend//ee/ by default. Add to packages/shared only what the public API/SDK contract actually requires — types, wire enums, validation shared with API consumers — or what is deliberately published for reuse; say which in your PR description. Recipe and doctrine content served by the platform itself (MCP skills, recipes under backend/skills/) stays in backend/ under the Community license by default; it moves under ee/ (with gated serving tools) only if it becomes a premium product asset.

By submitting a contribution, you agree to the Nodaro Contributor License Agreement. The same CLA covers individual and corporate contributions — Section 2 handles employer-permission. The cla-assistant bot will prompt for signature automatically on your first pull request.

If you’re contributing on behalf of an employer, double-check that your employer’s IP policy allows you to do so before signing. Contributions to ee/ directories or *.ee.{ext} files are subject to the Enterprise License; the CLA grants Nodaro the right to relicense all contributions across the full dual-licensing model.


Thanks for reading this far. The fastest path to a merged PR is: read CLAUDE.md, pick a small first change, open a draft PR early, and ask questions in Discussions when you’re stuck. We’re glad you’re here.