# Secrets API

The `ctx.majk.secrets` API provides secure storage and retrieval of sensitive information like API keys, tokens, and credentials. Secrets are scoped to global, project, or integration levels for proper isolation and security.

## Interface

```typescript
export interface SecretsAPI {
  set(key: string, value: string, scope?: SecretScope): Promise<void>;
  get(key: string, scope?: SecretScope): Promise<string | null>;
  has(key: string, scope?: SecretScope): Promise<boolean>;
  list(scope?: SecretScope): Promise<SecretInfo[]>;
  delete(key: string, scope?: SecretScope): Promise<boolean>;
  forProject(projectId: string): ScopedSecretsAPI;
  forIntegration(integrationId: string): ScopedSecretsAPI;
}

export interface SecretScope {
  type: 'global' | 'project' | 'integration';
  id?: string;
}

export interface SecretInfo {
  key: string;
  scope: SecretScope;
  description?: string;
  createdAt: Date;
  updatedAt: Date;
  lastUsedAt?: Date;
  tags?: string[];
}

export interface ScopedSecretsAPI {
  set(key: string, value: string): Promise<void>;
  get(key: string): Promise<string | null>;
  has(key: string): Promise<boolean>;
  list(): Promise<string[]>;
  delete(key: string): Promise<boolean>;
}
```

## Quick Start

```typescript
// Store an API key globally
await ctx.majk.secrets.set('openai.apiKey', 'sk-...', { type: 'global' });

// Retrieve the API key
const apiKey = await ctx.majk.secrets.get('openai.apiKey', { type: 'global' });

// Store project-specific secrets
await ctx.majk.secrets.set('database.password', 'secret123', {
  type: 'project',
  id: 'my-project-id'
});

// Use scoped API for cleaner code
const projectSecrets = ctx.majk.secrets.forProject('my-project-id');
await projectSecrets.set('aws.accessKey', 'AKIA...');
const accessKey = await projectSecrets.get('aws.accessKey');
```

## Methods

### `set(key: string, value: string, scope?: SecretScope): Promise<void>`

Stores a secret value securely. If no scope is provided, defaults to global scope.

```typescript
// Global secret (available across all projects)
await ctx.majk.secrets.set('github.token', 'ghp_xxxxxxxxxxxx', { type: 'global' });

// Project-specific secret
await ctx.majk.secrets.set('api.key', 'abc123', {
  type: 'project',
  id: 'ecommerce-app'
});

// Integration-specific secret
await ctx.majk.secrets.set('webhook.secret', 'wh_secret', {
  type: 'integration',
  id: 'slack-integration'
});
```

### `get(key: string, scope?: SecretScope): Promise<string | null>`

Retrieves a secret value. Returns `null` if the secret doesn't exist.

```typescript
// Get global secret
const githubToken = await ctx.majk.secrets.get('github.token', { type: 'global' });

// Get project secret with fallback
const apiKey = await ctx.majk.secrets.get('api.key', { type: 'project', id: 'my-project' }) || 'default-key';

// Check if secret exists before using
if (await ctx.majk.secrets.has('openai.apiKey')) {
  const openaiKey = await ctx.majk.secrets.get('openai.apiKey');
  // Use the key safely
}
```

### `has(key: string, scope?: SecretScope): Promise<boolean>`

Checks if a secret exists without retrieving its value.

```typescript
// Check for required secrets before starting operation
const requiredSecrets = ['aws.accessKey', 'aws.secretKey', 'aws.region'];
const projectId = 'deployment-project';

for (const secret of requiredSecrets) {
  if (!await ctx.majk.secrets.has(secret, { type: 'project', id: projectId })) {
    throw new Error(`Missing required secret: ${secret}`);
  }
}
```

### `list(scope?: SecretScope): Promise<SecretInfo[]>`

Lists all secrets in the specified scope with metadata (but not values).

```typescript
// List all global secrets
const globalSecrets = await ctx.majk.secrets.list({ type: 'global' });
console.log('Global secrets:', globalSecrets.map(s => s.key));

// List project secrets
const projectSecrets = await ctx.majk.secrets.list({
  type: 'project',
  id: 'my-project'
});

// Check when secret was last used
projectSecrets.forEach(secret => {
  console.log(`${secret.key} last used: ${secret.lastUsedAt || 'never'}`);
});
```

### `delete(key: string, scope?: SecretScope): Promise<boolean>`

Deletes a secret. Returns `true` if the secret was deleted, `false` if it didn't exist.

```typescript
// Delete a specific secret
const wasDeleted = await ctx.majk.secrets.delete('old.apiKey', {
  type: 'project',
  id: 'legacy-project'
});

if (wasDeleted) {
  console.log('Secret deleted successfully');
}
```

## Scoped APIs

For cleaner code when working with many secrets in the same scope, use the scoped APIs.

### `forProject(projectId: string): ScopedSecretsAPI`

Returns a scoped API for project-specific secrets.

```typescript
const projectSecrets = ctx.majk.secrets.forProject('ecommerce-app');

// All operations are automatically scoped to this project
await projectSecrets.set('stripe.publishableKey', 'pk_test_...');
await projectSecrets.set('stripe.secretKey', 'sk_test_...');

const publishableKey = await projectSecrets.get('stripe.publishableKey');

// List only returns keys (not full SecretInfo objects)
const allKeys = await projectSecrets.list();
console.log('Project secrets:', allKeys); // ['stripe.publishableKey', 'stripe.secretKey']
```

### `forIntegration(integrationId: string): ScopedSecretsAPI`

Returns a scoped API for integration-specific secrets.

```typescript
const slackIntegration = ctx.majk.secrets.forIntegration('slack-workspace-123');

await slackIntegration.set('bot.token', 'xoxb-...');
await slackIntegration.set('signing.secret', 'abc123...');
await slackIntegration.set('webhook.url', 'https://hooks.slack.com/...');

// Check if integration is configured
if (await slackIntegration.has('bot.token')) {
  const botToken = await slackIntegration.get('bot.token');
  // Initialize Slack client
}
```

## Secret Scopes

### Global Scope (`{ type: 'global' }`)
- Available across all projects and integrations
- Use for user-wide credentials (GitHub tokens, OpenAI keys)
- Highest level of access

```typescript
// User's personal GitHub token
await ctx.majk.secrets.set('github.personalToken', 'ghp_...', { type: 'global' });

// OpenAI API key for all projects
await ctx.majk.secrets.set('openai.apiKey', 'sk-...', { type: 'global' });
```

### Project Scope (`{ type: 'project', id: 'project-id' }`)
- Isolated to specific project
- Use for project-specific credentials and configuration
- Perfect for environment variables and deployment secrets

```typescript
const projectId = 'my-web-app';

// Database credentials for this project only
await ctx.majk.secrets.set('db.host', 'localhost', { type: 'project', id: projectId });
await ctx.majk.secrets.set('db.password', 'secret123', { type: 'project', id: projectId });

// API keys specific to this project's environment
await ctx.majk.secrets.set('stripe.secretKey', 'sk_test_...', { type: 'project', id: projectId });
```

### Integration Scope (`{ type: 'integration', id: 'integration-id' }`)
- Isolated to specific integration instance
- Use for third-party service connections
- Allows multiple instances of same integration type

```typescript
// Slack workspace A
const slackA = ctx.majk.secrets.forIntegration('slack-workspace-a');
await slackA.set('bot.token', 'xoxb-111...');

// Slack workspace B
const slackB = ctx.majk.secrets.forIntegration('slack-workspace-b');
await slackB.set('bot.token', 'xoxb-222...');

// Discord server
const discord = ctx.majk.secrets.forIntegration('discord-server-123');
await discord.set('bot.token', 'ODc...');
```

## Common Patterns

### Plugin Configuration with Secrets

```typescript
// Check for required secrets during plugin initialization
async function validatePluginSecrets(): Promise<boolean> {
  const requiredGlobalSecrets = ['openai.apiKey'];
  const requiredProjectSecrets = ['database.url', 'api.secret'];

  // Check global secrets
  for (const key of requiredGlobalSecrets) {
    if (!await ctx.majk.secrets.has(key, { type: 'global' })) {
      console.error(`Missing required global secret: ${key}`);
      return false;
    }
  }

  // Check project secrets
  const projectId = ctx.project?.id;
  if (projectId) {
    for (const key of requiredProjectSecrets) {
      if (!await ctx.majk.secrets.has(key, { type: 'project', id: projectId })) {
        console.error(`Missing required project secret: ${key}`);
        return false;
      }
    }
  }

  return true;
}
```

### Secret Rotation

```typescript
async function rotateApiKey(keyName: string, scope: SecretScope) {
  // Generate new API key (implementation depends on service)
  const newApiKey = await generateNewApiKey();

  // Store the new key
  await ctx.majk.secrets.set(keyName, newApiKey, scope);

  // Optionally keep backup of old key temporarily
  const oldKey = await ctx.majk.secrets.get(keyName, scope);
  if (oldKey) {
    await ctx.majk.secrets.set(`${keyName}.backup`, oldKey, scope);
  }

  console.log(`API key ${keyName} rotated successfully`);
}
```

### Environment-Specific Secrets

```typescript
async function getEnvironmentSecret(key: string, environment: string, projectId: string) {
  // Try environment-specific secret first
  const envKey = `${environment}.${key}`;
  let secret = await ctx.majk.secrets.get(envKey, { type: 'project', id: projectId });

  // Fallback to general secret
  if (!secret) {
    secret = await ctx.majk.secrets.get(key, { type: 'project', id: projectId });
  }

  // Fallback to global secret
  if (!secret) {
    secret = await ctx.majk.secrets.get(key, { type: 'global' });
  }

  return secret;
}

// Usage
const apiKey = await getEnvironmentSecret('api.key', 'production', 'my-project');
```

### Bulk Secret Management

```typescript
async function setupProjectSecrets(projectId: string, secrets: Record<string, string>) {
  const projectSecrets = ctx.majk.secrets.forProject(projectId);

  // Set all secrets
  const operations = Object.entries(secrets).map(([key, value]) =>
    projectSecrets.set(key, value)
  );

  await Promise.all(operations);
  console.log(`Set ${operations.length} secrets for project ${projectId}`);
}

// Usage
await setupProjectSecrets('new-project', {
  'db.host': 'postgres.example.com',
  'db.user': 'app_user',
  'db.password': 'secure_password',
  'redis.url': 'redis://localhost:6379',
  'jwt.secret': 'super-secret-jwt-key'
});
```

## Security Best Practices

### 1. Use Appropriate Scopes
- **Global**: Only for user-wide credentials (GitHub tokens, OpenAI keys)
- **Project**: For project-specific secrets (database passwords, API keys)
- **Integration**: For service-specific tokens and webhooks

### 2. Secret Key Naming
Use clear, hierarchical naming:
```typescript
// Good naming
'aws.accessKey'
'database.production.password'
'slack.workspace-123.webhook.secret'
'github.deployKey.private'

// Avoid vague names
'key1'
'secret'
'token'
```

### 3. Error Handling
```typescript
async function safeGetSecret(key: string, scope?: SecretScope): Promise<string> {
  try {
    const secret = await ctx.majk.secrets.get(key, scope);
    if (!secret) {
      throw new Error(`Secret '${key}' not found`);
    }
    return secret;
  } catch (error) {
    console.error(`Failed to retrieve secret '${key}':`, error);
    throw error;
  }
}
```

### 4. Secret Validation
```typescript
async function validateSecretFormat(key: string, scope?: SecretScope): Promise<boolean> {
  const secret = await ctx.majk.secrets.get(key, scope);
  if (!secret) return false;

  // Example: validate API key format
  if (key.includes('openai') && !secret.startsWith('sk-')) {
    console.warn(`Invalid OpenAI API key format for ${key}`);
    return false;
  }

  return true;
}
```

## Important Notes

- **Never log secret values**: Always use `has()` for existence checks in logs
- **Secrets are encrypted**: Values are securely stored and encrypted at rest
- **Automatic cleanup**: Unused secrets are tracked via `lastUsedAt` timestamp
- **Case sensitive**: Secret keys are case-sensitive (`api.key` ≠ `API.KEY`)
- **No nested objects**: Store complex data as JSON strings if needed

The Secrets API ensures your sensitive data is handled securely while providing the flexibility to organize secrets by scope and context.