pikku-config

$npx mdskill add pikkujs/pikku/pikku-config

Manage secrets, variables, and OAuth2 credentials safely.

  • Replaces unsafe process.env with typed secret services.
  • Depends on Pikku services and Zod schema validation.
  • Triggers on requests involving secrets, env vars, or OAuth.
  • Returns encrypted values through typed service functions.

SKILL.md

.github/skills/pikku-configView on GitHub ↗
---
name: pikku-config
description: 'Use when managing secrets, environment variables, config, or OAuth2 credentials in a Pikku app. Covers wireSecret, wireVariable, wireOAuth2Credential, and typed config access.
TRIGGER when: code uses wireSecret/wireVariable/wireOAuth2Credential, user asks about env vars, secrets, config, OAuth2, or "how do I access environment variables".
DO NOT TRIGGER when: user asks about API versioning/breaking changes (use pikku-versioning), service factories (use pikku-services), or auth middleware (use pikku-security).'
---

# Pikku Config, Secrets & OAuth2

Manage secrets, variables, and OAuth2 credentials. Never use `process.env` in Pikku functions — use typed services instead.

## Before You Start

```bash
pikku info functions --verbose   # See existing functions and their versions
pikku info tags --verbose        # Understand project organization
```

See `pikku-concepts` for the core mental model.

## Secrets & Variables

### `wireSecret(config)`

Declare a secret with a Zod schema for type-safe access:

```typescript
wireSecret({
  name: string, // Secret identifier
  schema: ZodSchema, // Shape and validation
})
```

### `wireVariable(config)`

Declare a variable (non-sensitive config) with a Zod schema:

```typescript
wireVariable({
  name: string,
  schema: ZodSchema,
})
```

### Accessing in Functions

```typescript
// Secrets — encrypted, sensitive values
const config = await services.secrets.getSecretJSON('SECRET_NAME')

// Variables — plain-text configuration
const flags = await services.variables.getVariableJSON('VARIABLE_NAME')

// Simple string access
const apiKey = services.variables.get('API_KEY')
```

### Local Development Services

```typescript
import { LocalSecretService, LocalVariablesService } from '@pikku/core/services'

const createSingletonServices = pikkuServices(async (config) => ({
  secrets: new LocalSecretService(), // Reads from .env or local files
  variables: new LocalVariablesService(), // Reads from environment
}))
```

### Usage Patterns

```typescript
// Declare secrets with typed schemas
wireSecret({
  name: 'STRIPE_CONFIG',
  schema: z.object({
    apiKey: z.string().startsWith('sk_'),
    webhookSecret: z.string(),
  }),
})

// In your function — fully typed
const config = await secrets.getSecretJSON('STRIPE_CONFIG')
// config.apiKey       → string (autocompleted)
// config.webhookSecret → string (autocompleted)

// Declare variables
wireVariable({
  name: 'FEATURE_FLAGS',
  schema: z.object({
    darkMode: z.boolean(),
    maxUploadMB: z.number().default(10),
  }),
})

// Read it — typed and validated
const flags = await variables.getVariableJSON('FEATURE_FLAGS')
// flags.darkMode    → boolean
// flags.maxUploadMB → number
```

## OAuth2 Credentials

### `wireOAuth2Credential(config)`

```typescript
wireOAuth2Credential({
  name: string,              // Credential identifier
  displayName: string,       // Human-readable name
  secretId: string,          // Secret holding { clientId, clientSecret }
  tokenSecretId: string,     // Secret for token storage (auto-refreshed)
  authorizationUrl: string,  // OAuth2 authorization endpoint
  tokenUrl: string,          // OAuth2 token endpoint
  scopes: string[],          // Required OAuth2 scopes
})
```

### Usage

```typescript
wireOAuth2Credential({
  name: 'slackOAuth',
  displayName: 'Slack OAuth',
  secretId: 'SLACK_OAUTH_APP',
  tokenSecretId: 'SLACK_OAUTH_TOKENS',
  authorizationUrl: 'https://slack.com/oauth/v2/authorize',
  tokenUrl: 'https://slack.com/api/oauth.v2.access',
  scopes: ['chat:write', 'channels:read'],
})

// In your function — tokens refresh automatically
const response = await slackOAuth.request(
  'https://slack.com/api/chat.postMessage',
  {
    method: 'POST',
    body: JSON.stringify({ channel, text }),
  }
)
const data = await response.json()
```

## Key Rule

**Never use `process.env` inside Pikku functions.** Use the `variables` or `secrets` service:

```typescript
// ❌ Wrong
const apiKey = process.env.API_KEY

// ✅ Correct
const apiKey = services.variables.get('API_KEY')
```

`process.env` belongs only in server bootstrap code (`start.ts`).

## Complete Example

```typescript
// schemas/config.ts
wireSecret({
  name: 'DATABASE_CONFIG',
  schema: z.object({
    connectionString: z.string().url(),
    maxPoolSize: z.number().default(10),
  }),
})

wireVariable({
  name: 'APP_CONFIG',
  schema: z.object({
    appName: z.string(),
    maxUploadSizeMB: z.number().default(10),
    maintenanceMode: z.boolean().default(false),
  }),
})

wireOAuth2Credential({
  name: 'githubOAuth',
  displayName: 'GitHub OAuth',
  secretId: 'GITHUB_OAUTH_APP',
  tokenSecretId: 'GITHUB_OAUTH_TOKENS',
  authorizationUrl: 'https://github.com/login/oauth/authorize',
  tokenUrl: 'https://github.com/login/oauth/access_token',
  scopes: ['read:user', 'repo'],
})

// functions/admin.functions.ts
export const getAppStatus = pikkuSessionlessFunc({
  title: 'Get App Status',
  func: async ({ variables, secrets }) => {
    const appConfig = await variables.getVariableJSON('APP_CONFIG')
    return {
      appName: appConfig.appName,
      maintenanceMode: appConfig.maintenanceMode,
    }
  },
})
```

More from pikkujs/pikku

SkillDescription
pikku-addon'Use when creating or consuming reusable function packages (addons) in Pikku. Covers wireAddon, addon(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, and cross-project function sharing.
pikku-ai-agent'Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers pikkuAIAgent, tool registration, memory, streaming, and agent invocation.
pikku-ai-vercel'Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers VercelAIAgentRunner for streaming and non-streaming AI agent steps.
pikku-ai-voice'Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a Pikku app. Covers voiceInput/voiceOutput middleware hooks and STT/TTS service interfaces.
pikku-auth-js'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthHandler, createAuthRoutes, and Auth.js configuration.
pikku-aws'Use when setting up AWS services (S3, SQS, Secrets Manager) in a Pikku app. Covers S3Content for file storage, SQSQueueService for queues, and AWSSecrets for secret management.
pikku-backblaze'Use when setting up Backblaze B2 file storage in a Pikku app. Covers B2Content for file uploads, downloads, and signed URLs.
pikku-cli'Use when building CLI commands with Pikku. Covers wireCLI, pikkuCLICommand, subcommands, options, parameters, custom renderers, and nested command groups.
pikku-concepts'Foundational guide to Pikku framework concepts. Use this skill when working with any Pikku codebase, starting a new Pikku project, or migrating a backend to Pikku. Covers the core mental model, function types, project structure, code generation, testing, and how Pikku maps to traditional backend patterns.
pikku-cron'Use when adding scheduled tasks, recurring jobs, or cron-based automation to a Pikku app. Covers wireScheduler, cron expressions, scheduled task wire object, and scheduler middleware.