Skip to content

Latest commit

 

History

214 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@smooai/config — One schema. Every language. Typed everywhere.

npm Smoo AI license

TypeScript Python Rust Go .NET Kotlin Swift

Features  ·  Install  ·  Quick start  ·  Platform


Define your config once with Zod (or Valibot, ArkType, Effect), and read it with full type inference everywhere — public config, server-only secrets, and live feature flags. Rename a key and every call site is a compile error, not a 3 AM page. Native clients in TypeScript, Python, Rust, Go, and .NET all read from the same source of truth.

📣 The CLI moved. Use smoo config from the smooth repo for all operator commands (login, get, set, list, push, pull, diff, init, etc.). The standalone smooai-config CLI that used to live in this repo is deprecated and being deleted (SMOODEV-1411). The runtime library @smooai/config (TypeScript / Python / Rust / Go / .NET, consumed via secretConfig.get(...) / publicConfig.get(...) / featureFlag.get(...)) is unchanged — only the operator CLI surface moved to Rust in the smooth repo. (smoo … is the th binary's platform namespace; th smoo … and the old th config … spelling also work.)

✨ Features

  • Three tiers, one schema — public config, secrets, and feature flags defined once with Zod/Valibot/ArkType/Effect, validated everywhere they're read.
  • Strongly-typed keysdefineConfig() gives you PublicConfigKeys, SecretConfigKeys, and FeatureFlagKeys with full inference. Mis-typed keys fail at compile time, not at runtime.
  • Any environment, any key — the same API for development, staging, and production. Override per-stage without touching code.
  • Zero-latency cold starts — values are baked into the bundle as env vars (Next.js, Vite) or resolved in-memory from a local runtime (server). No network round-trip on the hot path.
  • Browser, server, framework-native — the same typed keys read cleanly from React client components, Server Components, Next.js, Vite, or plain Node.
  • Live feature flags — toggled from the dashboard without a redeploy, but still typed.
  • Native clients in every language — TypeScript, Python, Rust, Go, and .NET (C#) server SDKs all read from the same source of truth, plus Kotlin and Swift mobile SDKs for the public-only app surface (ADR-074 mobile runtime mode).

Languages / SDKs

Pick the SDK that matches your service. Every server client reads the same schema, the same encrypted bundle, and the same config API — so a key renamed in one language ripples through all of them. The two mobile SDKs (📱) speak a deliberately narrower, public-only surface — no secrets ever ship to a device.

SDK One-liner README
TypeScript Primary SDK. Schema definition, Next.js / Vite plugins, server runtime, React hooks. README.md (this file)
Python Pydantic-validated schemas, sync ConfigClient, LocalConfigManager + ConfigManager, baked runtime. python/README.md
Go Native struct schemas, thread-safe ConfigClient / ConfigManager, baked-blob runtime. go/config/README.md
Rust JsonSchema-derived schemas, async ConfigClient, sync ConfigManager, baked-blob runtime. rust/config/README.md
.NET Roslyn source-generated typed keys, OAuth2 SmooConfigClient, AES-GCM SmooConfigRuntime. Thinner surface than the other server SDKs — see the capability notes. dotnet/README.md
Kotlin 📱 Mobile runtime mode (ADR-074): baked public bundle + live flag/limit evaluation, offline-safe, no secrets on device. Ships via JitPack commit pin — not on Maven Central yet. kotlin/ · spec
Swift 📱 Mobile runtime mode (ADR-074): same surface as Kotlin (publicValue, evaluateFlag, evaluateLimit). Consumed via SPM pinned to a revision — no version tag yet. swift/ · spec

The five server SDKs release in version lockstep — v6.11.3 on npm, PyPI, crates.io, and NuGet, with the matching v6.11.3 git tag for the Go module, all cut from the same commit (verified against each registry 2026-08-20). The mobile SDKs version separately (commit-pinned; see below).

📦 Install

pnpm add @smooai/config

🚀 Quick Start (TypeScript)

1. Define your configuration schema

Use defineConfig() with any StandardSchema-compliant library (Zod, Valibot, ArkType, Effect Schema) or the built-in StringSchema, BooleanSchema, and NumberSchema helpers:

// .smooai-config/config.ts
import { defineConfig, StringSchema, BooleanSchema, NumberSchema } from '@smooai/config';
import { z } from 'zod';

const config = defineConfig({
    publicConfigSchema: {
        apiBaseUrl: z.string().url(),
        maxRetries: NumberSchema,
        enableDebug: BooleanSchema,
    },
    secretConfigSchema: {
        databaseUrl: z.string().url(),
        apiKey: StringSchema,
    },
    featureFlagSchema: {
        enableNewUi: BooleanSchema,
        betaFeatures: BooleanSchema,
    },
});

export default config;

// Extract typed key objects for use throughout your app
export const { FeatureFlagKeys, PublicConfigKeys, SecretConfigKeys } = config;

defineConfig() automatically maps camelCase keys to UPPER_SNAKE_CASE:

FeatureFlagKeys.ENABLE_NEW_UI; // "ENABLE_NEW_UI"
PublicConfigKeys.API_BASE_URL; // "API_BASE_URL"
SecretConfigKeys.DATABASE_URL; // "DATABASE_URL"

2. Add to tsconfig.json

{
    "compilerOptions": { ... },
    "include": ["src/**/*", ".smooai-config/**/*.ts"]
}

📖 Next.js Integration

Inject config into next.config.ts

Use withSmooConfig() to inject feature flags and public config as NEXT_PUBLIC_ environment variables, with per-stage overrides:

// next.config.ts
import { withSmooConfig } from '@smooai/config/nextjs/withSmooConfig';

const nextConfig = withSmooConfig({
    default: {
        featureFlags: { enableNewUi: false, betaFeatures: false },
        publicConfig: { apiBaseUrl: 'https://api.smooai.com', maxRetries: 3 },
    },
    development: {
        featureFlags: { enableNewUi: true },
        publicConfig: { apiBaseUrl: 'http://localhost:3000' },
    },
});

export default nextConfig;

This sets environment variables like NEXT_PUBLIC_FEATURE_FLAG_ENABLE_NEW_UI=true and NEXT_PUBLIC_CONFIG_API_BASE_URL=http://localhost:3000 based on the current stage.

Read config in React client components

import { getClientFeatureFlag, getClientPublicConfig } from '@smooai/config/client';

function MyComponent() {
    const isNewUi = getClientFeatureFlag('enableNewUi');
    const apiUrl = getClientPublicConfig('apiBaseUrl');

    if (!isNewUi) return <LegacyUI />;
    return <NewUI apiUrl={apiUrl} />;
}

These functions check NEXT_PUBLIC_FEATURE_FLAG_* and NEXT_PUBLIC_CONFIG_* env vars automatically — no provider needed, no loading state.

Server Components + Client hydration (zero loading flash)

For apps that need runtime config from a config server, use getConfig on the server and SmooConfigProvider to hydrate client components:

// app/layout.tsx (Server Component)
import { getConfig, SmooConfigProvider } from '@smooai/config/nextjs';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
    const config = await getConfig({
        environment: 'production',
        fetchOptions: { next: { revalidate: 60 } },
    });

    return (
        <html>
            <body>
                <SmooConfigProvider
                    initialValues={config}
                    baseUrl={process.env.SMOOAI_CONFIG_API_URL}
                    apiKey={process.env.SMOOAI_CONFIG_API_KEY}
                    orgId={process.env.SMOOAI_CONFIG_ORG_ID}
                    environment="production"
                >
                    {children}
                </SmooConfigProvider>
            </body>
        </html>
    );
}
// Any client component — values available synchronously (pre-seeded from SSR)
import { usePublicConfig, useFeatureFlag } from '@smooai/config/nextjs';

function Dashboard() {
    const { value: apiUrl } = usePublicConfig<string>('API_BASE_URL');
    const { value: enableNewUi } = useFeatureFlag<boolean>('ENABLE_NEW_UI');
    return (
        <div>
            API: {apiUrl}, New UI: {String(enableNewUi)}
        </div>
    );
}

📖 Vite Integration

Vite plugin

// vite.config.ts
import { defineConfig } from 'vite';
import { smooConfigPlugin } from '@smooai/config/vite/smooConfigPlugin';

export default defineConfig({
    plugins: [
        smooConfigPlugin({
            featureFlags: { enableNewUi: true, betaFeatures: false },
            publicConfig: { apiBaseUrl: 'http://localhost:3000' },
        }),
    ],
});

Then read values the same way as Next.js — getClientFeatureFlag and getClientPublicConfig from @smooai/config/client check VITE_FEATURE_FLAG_* and VITE_CONFIG_* automatically.

Preload config (optional)

For runtime config from a config server, start fetching before React mounts:

// main.tsx
import { preloadConfig, ConfigProvider } from '@smooai/config/vite';
import { createRoot } from 'react-dom/client';

preloadConfig({ environment: 'production' });

createRoot(document.getElementById('root')!).render(
    <ConfigProvider baseUrl="https://config.smooai.dev" apiKey="your-public-key" orgId="your-org-id" environment="production">
        <App />
    </ConfigProvider>,
);

📖 Server-Side Config Access

For Node.js server code, use buildConfigObject to get sync and async accessors with full type safety:

import buildConfigObject from '@smooai/config/platform/server';
import config, { PublicConfigKeys, SecretConfigKeys, FeatureFlagKeys } from './.smooai-config/config';

const configObj = buildConfigObject(config);

// Sync access (uses worker threads)
const dbUrl = configObj.secretConfig.getSync(SecretConfigKeys.DATABASE_URL);
const apiUrl = configObj.publicConfig.getSync(PublicConfigKeys.API_BASE_URL);
const isNewUi = configObj.featureFlag.getSync(FeatureFlagKeys.ENABLE_NEW_UI);

// Async access
const apiKey = await configObj.secretConfig.getAsync(SecretConfigKeys.API_KEY);

How .getSync() works (and how to ship it in any bundled compute)

Sync accessors run an async config read to completion on the caller thread via synckit — a Node Worker pool + Atomics.wait on a SharedArrayBuffer. createSyncFn only accepts a file:// URL, so the worker body has to live on disk. The SDK resolves it in two stages:

  1. Sidecar filesync-worker.mjs sitting next to the compiled SDK entry (i.e. resolved via new URL('./sync-worker.mjs', import.meta.url) from dist/server/index.mjs). This is the normal case for plain Node resolution with no bundling — node_modules/@smooai/config/dist/server/sync-worker.mjs is already there. It's also the preferred case when bundlers copy the sidecar into the deploy output. Zero /tmp writes.

  2. Extract-to-/tmp fallback — if the sidecar isn't on disk at that path (e.g. a bundler inlined the SDK entry into a single file and didn't copy the sidecar), the SDK writes an embedded copy of the worker source to mkdtempSync()/sync-worker.mjs once per process and hands that path to synckit. One ~1-2 MiB write at cold start, amortised across every sync read for the lifetime of the process. Works anywhere with a writable temp dir.

Both paths are transparent — your code is identical either way. Which path you land on depends on how your compute is packaged.

Plain Node (no bundling)

Zero config. The SDK resolves node_modules/@smooai/config/dist/server/sync-worker.mjs directly — path (1) every time.

Any bundled compute (Lambda, Cloud Run, ECS, container, Worker, etc.)

The rule is universal: if your build inlines the SDK entry into a single output file, you need to ship sync-worker.mjs next to that output (or accept path (2)'s /tmp write once per cold start).

The source path is always:

node_modules/@smooai/config/dist/server/sync-worker.mjs

The destination is alongside whichever file ends up being your runtime's import.meta.url anchor — typically the bundled handler .mjs / .js.

Recipes for common setups:

esbuild — explicit copy plugin

// build.ts
import { build } from 'esbuild';
import { copy } from 'esbuild-plugin-copy';

await build({
    entryPoints: ['src/handler.ts'],
    outdir: 'dist',
    bundle: true,
    format: 'esm',
    platform: 'node',
    plugins: [
        copy({
            assets: {
                from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs',
                to: 'dist/sync-worker.mjs',
            },
        }),
    ],
});

tsup — onSuccess hook

// tsup.config.ts
export default defineConfig({
    entry: ['src/handler.ts'],
    format: ['esm'],
    onSuccess: 'cp node_modules/@smooai/config/dist/server/sync-worker.mjs dist/sync-worker.mjs',
});

Serverless Framework — package.include

package:
    patterns:
        - 'node_modules/@smooai/config/dist/server/sync-worker.mjs'

Or copy into the handler dir as a build step and include from there.

AWS SAM — CodeUri + build script

Add a Makefile / build script that copies sync-worker.mjs into the BuildArtifactPath alongside your handler.

SST (AWS) — per-function or via $transform

// sst.config.ts — per function
new sst.aws.Function('Api', {
    handler: 'src/api.handler',
    copyFiles: [{ from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs' }],
});

// Or at the stack level via $transform (every Function gets it automatically)
$transform(sst.aws.Function, (fn) => {
    fn.copyFiles = [...(fn.copyFiles ?? []), { from: 'node_modules/@smooai/config/dist/server/sync-worker.mjs' }];
});

Docker container (ECS, Cloud Run, anywhere)

# After your main build step, ensure the sidecar is next to the bundled entry.
COPY --from=build /app/dist/server.mjs /app/
COPY --from=build /app/node_modules/@smooai/config/dist/server/sync-worker.mjs /app/
CMD ["node", "server.mjs"]

If your build step keeps node_modules in the final image, no extra copy is needed — the SDK resolves the sidecar from node_modules/ path (1) directly.

When the sidecar truly can't be shipped

Path (2) — the /tmp extraction — is the safety net. One ~1-2 MiB write at cold start, then synckit re-uses the file for the rest of the process lifetime. Lambda's 512 MiB–10 GiB /tmp easily absorbs this; containers with an ephemeral /tmp work the same way. You can ignore this whole section and .getSync() will still work — you're just paying one filesystem write per cold start.

Edge runtimes (Vercel Edge, Cloudflare Workers)

These runtimes don't expose Node's worker_threads at all, so .getSync() is a no-go there by design. Use .get() (async) everywhere that needs to run on the edge. The error surface makes this explicit if you try.

📖 React Hooks (framework-agnostic)

For any React app using the runtime config client:

import { ConfigProvider, usePublicConfig, useFeatureFlag } from '@smooai/config/react';

function App() {
    return (
        <ConfigProvider baseUrl="https://config.smooai.dev" apiKey="your-api-key" orgId="your-org-id" environment="production">
            <MyComponent />
        </ConfigProvider>
    );
}

function MyComponent() {
    const { value: apiUrl, isLoading, error } = usePublicConfig<string>('API_BASE_URL');
    const { value: enableNewUi } = useFeatureFlag<boolean>('ENABLE_NEW_UI');

    if (isLoading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;

    return (
        <div>
            API URL: {apiUrl}, New UI: {String(enableNewUi)}
        </div>
    );
}

📖 SDK Runtime Client

All language implementations include a runtime client for fetching configuration values from the Smoo AI config server with local caching.

Environment Variables

Authentication is OAuth2 client_credentials against {authUrl}/token — the client exchanges (CLIENT_ID, CLIENT_SECRET) for a JWT and uses that JWT as the Bearer token on every config call. TokenProvider caches the JWT in memory and refreshes 60s before expiry.

Variable Description Required
SMOOAI_CONFIG_API_URL Base URL of the config API Yes
SMOOAI_CONFIG_AUTH_URL OAuth issuer base URL (defaults to https://auth.smoo.ai) No
SMOOAI_CONFIG_CLIENT_ID OAuth client ID Yes
SMOOAI_CONFIG_CLIENT_SECRET OAuth client secret (legacy SMOOAI_CONFIG_API_KEY is accepted as a fallback) Yes
SMOOAI_CONFIG_ORG_ID Organization ID Yes
SMOOAI_CONFIG_ENV Default environment name (defaults to "development") No

Migration note (v5 / SMOODEV-974): the TypeScript ConfigClient previously sent SMOOAI_CONFIG_API_KEY directly as the Bearer token, which the backend rejected with 401 because it expects a JWT. The SDK now mints a JWT via the OAuth client_credentials grant before each call — matching the .NET client, the in-package bootstrap, and the CLI. You must set SMOOAI_CONFIG_CLIENT_ID in addition to SMOOAI_CONFIG_API_KEY / SMOOAI_CONFIG_CLIENT_SECRET for the runtime SDK to work. The legacy SMOOAI_CONFIG_API_KEY env var continues to function as the OAuth client secret.

TypeScript Client

import { ConfigClient } from '@smooai/config/platform/client';

// Zero-config (reads from env vars — needs CLIENT_ID + CLIENT_SECRET/API_KEY + ORG_ID)
const client = new ConfigClient();

// Or explicit
const client = new ConfigClient({
    baseUrl: 'https://config.smooai.dev',
    authUrl: 'https://auth.smooai.dev',
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    orgId: 'your-org-id',
    environment: 'production',
});

const apiUrl = await client.getValue('API_BASE_URL');
const allValues = await client.getAllValues();
client.invalidateCache();

📖 Container / Runtime Mode (EKS / ECS)

The baked blob tier is the blessed path for Lambda, but it is the wrong default for long-lived containers: when the per-build blob key isn't delivered to the pod, resolution silently falls through to the (absent) file tier and returns undefined for a required secret. That caused a real outage — a container got undefined for STRIPE_API_KEY, new Stripe(undefined) threw at module load, the process exited 0 before listen(), and the pod CrashLooped with the root cause buried (SMOODEV-1478).

Container mode makes the HTTP config API the first-class path for containers, authenticated with an OAuth2 client_credentials (M2M) token, and fails loud: a required value that doesn't resolve throws a typed error instead of returning undefined.

Containers use container mode, not the baked blob. See docs/Container-Runtime-Mode.md for the full env contract, a complete ExternalSecret (External Secrets Operator) recipe, and a readiness-probe example.

import { initContainerConfig, ConfigKeyUnresolvedError } from '@smooai/config/container';
import schema from '../.smooai-config/config';

// Validates the container env, mints a token, and does an initial fetch —
// startup fails LOUD here (throws), not on first read.
const config = await initContainerConfig({ schema });

// Fail-loud: a required secret that doesn't resolve throws
// ConfigKeyUnresolvedError instead of returning undefined.
const stripeKey = await config.secretConfig.get('stripeApiKey');

// Kubernetes readiness probe — never throws.
app.get('/healthz/config', (_req, res) => {
    const h = config.health(); // { status: 'healthy' } | { status: 'unhealthy', reason }
    res.status(h.status === 'healthy' ? 200 : 503).json(h);
});

Env contract (identical in every SDK): SMOOAI_CONFIG_API_URL, SMOOAI_CONFIG_CLIENT_ID, SMOOAI_CONFIG_CLIENT_SECRET, SMOOAI_CONFIG_ORG_ID, SMOOAI_CONFIG_ENV (all required), plus optional SMOOAI_CONFIG_AUTH_URL and SMOOAI_CONFIG_MODE=container (to force the mode). All schema-declared keys are treated as required by default; opt specific keys out with initContainerConfig({ optionalKeys: ['...'] }).

📖 Configuration Tiers

Tier Purpose Examples
Public Client-visible settings API URLs, feature toggles, UI config
Secret Server-side only Database URLs, API keys, JWT secrets
Feature Flags Runtime toggles A/B tests, gradual rollouts, beta access

Security: B2M Key Restrictions

Operation B2M (Public Key) M2M (Secret Key)
Read public values Yes Yes
Read feature flags Yes Yes
Read secret values No (filtered) Yes
Write config values No (403) Yes
Delete config values No (403) Yes

Browser-to-Machine (B2M) keys are designed for browser clients. Secret-tier values are automatically filtered. B2M keys are read-only for public and feature flag tiers.

Machine-to-Machine (M2M) keys have full access to all tiers and write operations.

📖 Multi-Language Support

@smooai/config has native server implementations in Python, Rust, Go, and .NET (C#) alongside the primary TypeScript package, plus mobile SDKs in Kotlin and Swift (a deliberately different, public-only surface — see below). Every server client reads the same encrypted bundle, the same schema, and the same config API. See the per-SDK READMEs linked above for full usage docs — the snippets below are five-line orientation only.

Python — see python/README.md

pip install smooai-config
# or: uv add smooai-config
from smooai_config.client import ConfigClient

with ConfigClient() as client:  # reads SMOOAI_CONFIG_* env vars
    value = client.get_value("API_URL", environment="production")
cargo add smooai-config
use smooai_config::ConfigClient;

let mut client = ConfigClient::from_env();
let value = client.get_value("API_URL", None).await?;
go get github.com/SmooAI/config/go/config
import "github.com/SmooAI/config/go/config"

client := config.NewConfigClientFromEnv()
defer client.Close()
value, _ := client.GetValue("API_URL", "production")

.NET — see dotnet/README.md

dotnet add package SmooAI.Config
using SmooAI.Config;
using SmooAI.Config.Runtime;

var runtime = SmooConfigRuntime.Load();  // reads SMOO_CONFIG_KEY_FILE + SMOO_CONFIG_KEY
using var client = new SmooConfigClient(options);
var apiUrl = await Public.ApiUrl.ResolveAsync(runtime, client);

Kotlin (mobile) — see docs/Mobile-Runtime-Mode-Spec.md

Ships via JitPack pinned to a commit (Maven Central under ai.smoo is the planned follow-up — the in-repo Gradle version is a dev default, so don't pin a version, pin a SHA):

// settings.gradle.kts / build.gradle.kts
repositories { maven("https://jitpack.io") }
dependencies { implementation("com.github.SmooAI:config:<commit-sha>") }
import ai.smoo.config.SmooConfig
import ai.smoo.config.SmooConfigOptions

val config = SmooConfig(SmooConfigOptions(environment = "production", engine = engine, bundledConfigFile = bakedBundle))
val apiUrl = config.publicValue("API_BASE_URL")                       // baked bundle → refreshed cache, offline-safe
val newUi = config.evaluateFlag("ENABLE_NEW_UI", default = false)     // http → disk cache → default

Swift (mobile) — see docs/Mobile-Runtime-Mode-Spec.md

Consumed via Swift Package Manager pinned to a revision (there is no SPM version tag yet; the root Package.swift exists so SPM can resolve the repo URL directly):

// Package.swift
.package(url: "https://github.com/SmooAI/config", revision: "<commit-sha>")
import SmooAIConfig

let config = SmooConfig(options: SmooConfigOptions(environment: "production", bundledConfigURL: bakedBundleURL))
let apiUrl = config.publicValue(forKey: "API_BASE_URL")
let newUi = await config.evaluateFlag("ENABLE_NEW_UI", default: false)

Mobile binaries are attacker-owned territory, so the mobile SDKs speak only the public app-config surface (/config/app/...): a baked public bundle (plaintext — it never contains secrets) plus live feature-flag / limit evaluation with an offline disk cache. There is no secret tier, no M2M credential, and no schema/LocalConfigManager surface on device — by design, per ADR-074.

SDK capability notes

Honest asymmetries between the SDKs, so you can pick with your eyes open:

Capability TS Python Rust Go .NET Kotlin / Swift
Encrypted baked bundle + config API reads 📱 public-only bundle
Local config-file workflow (LocalConfigManager in Py/Rust/Go/.NET; the file tier in TS)
Cloud-region resolution
Deferred values / merge_replace_arrays semantics
Shared schema-validation conformance fixture (test-fixtures/schema-validation-cases.json)
Live feature flags ✅ (+ limits)

The .NET SDK reached parity with the other four server SDKs in 6.11.x: LocalConfigManager, cloud-region resolution, deferred values and MergeReplaceArrays all landed, and its schema validator is held to the same test-fixtures/schema-validation-cases.json corpus. Cross-language schema-validation parity is now a TS/Python/Rust/Go/.NET guarantee. Kotlin and Swift stay outside it by design — there is no schema surface on device (ADR-074).

📖 Development

Prerequisites

  • Node.js 22+, pnpm 10+
  • Python 3.13+ with uv (for the Python package)
  • Rust toolchain (for the Rust package)
  • Go 1.22+ (for the Go package)

Commands

pnpm install               # Install dependencies
pnpm build                 # Build all packages (TS, Python, Rust, Go)
pnpm test                  # Run all tests (Vitest, pytest, cargo test, go test)
pnpm lint                  # Lint all code (oxlint, ruff, clippy, go vet)
pnpm format                # Format all code (oxfmt, ruff, cargo fmt, gofmt)
pnpm typecheck             # Type check (tsc, basedpyright, cargo check)
pnpm check-all             # Full CI parity check

Schema Libraries

Supports Zod, Valibot, ArkType, Effect Schema, and built-in schema types. See SCHEMA_USAGE.md for examples with each library.

🧩 Part of Smoo AI

@smooai/config is built and open-sourced by Smoo AI — the AI-powered business platform with AI built into every product: CRM, customer support, campaigns, field service, observability, and developer tools.

🤝 Contributing

Contributions are welcome. This project uses changesets to manage versions and releases.

  1. Fork the repository.
  2. Create your branch (git checkout -b amazing-feature).
  3. Make your changes.
  4. Add a changeset: pnpm changeset.
  5. Commit and push.
  6. Open a pull request.

📄 License

MIT © SmooAI. See LICENSE.

Contact

Brent Rager

Smoo GitHub: github.com/SmooAI


Built by Smoo AI — AI built into every product.

About

Type-safe multi-language configuration management — schema validation, three-tier config (public, secrets, feature flags), and runtime clients for TypeScript, Python, Rust, and Go.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages