# API Reference

Quick reference for @majkapp/plugin-kit API.

## definePlugin()

```typescript
import { definePlugin } from '@majkapp/plugin-kit';

const plugin = definePlugin(id, name, version)
  .pluginRoot(__dirname)  // REQUIRED FIRST
  .function(/* ... */)
  .screenReact(/* ... */)
  .service(/* ... */)
  .onReady(/* ... */)
  .build();

export = plugin;
```

### Parameters
- `id` - Unique plugin ID (lowercase, hyphens)
- `name` - Display name
- `version` - Semantic version (e.g., '1.0.0')

## .pluginRoot()

**REQUIRED.** Must be called first.

```typescript
.pluginRoot(__dirname)  // Enables resource loading
```

## .function()

Define a callable function.

```typescript
.function(name, {
  description: string,        // REQUIRED: 2-3 sentences
  input: JsonSchema,          // REQUIRED: Input validation schema
  output: JsonSchema,         // REQUIRED: Output validation schema
  handler: async (input, ctx) => { /* ... */ },  // REQUIRED
  tags: string[],             // Optional: ['monitoring', 'health']
  deprecated: boolean         // Optional: Mark as deprecated
})
```

### Handler Context (ctx)

```typescript
handler: async (input, ctx) => {
  // Logging
  ctx.logger.debug(message, data);
  ctx.logger.info(message, data);
  ctx.logger.warn(message, data);
  ctx.logger.error(message, data);

  // Storage
  await ctx.storage.get(key);
  await ctx.storage.set(key, value);
  await ctx.storage.has(key);
  await ctx.storage.delete(key);
  await ctx.storage.clear();

  // RPC - Inter-plugin communication
  await ctx.rpc.registerService(serviceName, implementation);
  await ctx.rpc.unregisterService(serviceName);
  const proxy = await ctx.rpc.createProxy<T>(serviceName);
  const callback = await ctx.rpc.createCallback!(fn, options);  // Optional
  ctx.rpc.cleanupCallback!(callback);                            // Optional
  const stats = ctx.rpc.getCallbackStats!();                     // Optional

  // MAJK APIs
  await ctx.majk.conversations.list();
  await ctx.majk.conversations.get(id);
  await ctx.majk.todos.list();
  await ctx.majk.todos.get(id);
  await ctx.majk.projects.list();
  await ctx.majk.projects.get(id);
  await ctx.majk.teammates.list();
  await ctx.majk.teammates.get(id);
  await ctx.majk.mcpServers.list();
  await ctx.majk.mcpServers.get(id);
  await ctx.majk.eventBus.subscribeAll(handler);  // Use in .onReady()

  // Plugin Self API (ctx.majk.me) - identity-scoped, pre-bound to this plugin's ID
  await ctx.majk.me.teammates();                          // list plugin's own teammates
  await ctx.majk.me.getDefaultTeammate();                 // get default teammate or null
  await ctx.majk.me.conversations({ status: 'active' }); // list owned conversations
  const chat = await ctx.majk.me.startChat({ message: 'Do the work' }); // spawn background chat
  const handle = await ctx.majk.me.attachChat(conversationId);          // reattach to owned chat
  // chat.send(), chat.waitForCompletion(), chat.onMessage(), chat.dispose()  -- see --llm:me

  return { /* output matching schema */ };
}
```

## .screenReact()

Define a React screen.

```typescript
.screenReact({
  id: string,                              // Unique ID
  name: string,                            // Display name
  description: string,                     // 2-3 sentences
  route: `/plugin-screens/${id}/path`,    // MUST start with /plugin-screens/{id}/
  pluginPath: string,                      // Path to built React app (e.g., '/index.html')
  pluginPathHash: string                   // Optional: Hash route (e.g., '#/dashboard')
})
```

### Multiple Screens

```typescript
.screenReact({
  id: 'my-plugin-dashboard',
  name: 'Dashboard',
  description: 'Main dashboard',
  route: '/plugin-screens/my-plugin/dashboard',
  pluginPath: '/index.html',
  pluginPathHash: '#/'
})

.screenReact({
  id: 'my-plugin-settings',
  name: 'Settings',
  description: 'Plugin settings',
  route: '/plugin-screens/my-plugin/settings',
  pluginPath: '/index.html',
  pluginPathHash: '#/settings'
})
```

## .service()

Group functions into a service.

```typescript
.service(serviceName, {
  type: string,                      // Service type identifier
  metadata: {
    name: string,                    // Display name
    description: string,             // Service description
    version: string,                 // Service version
    author?: string,
    homepage?: string,
    tags?: string[]
  },
  discoverable: boolean              // Allow discovery by other plugins
})
  .withFunction(functionName, {
    examples?: string[],             // Usage examples
    tags?: string[],                 // Additional tags
    metadata?: Record<string, any>   // Custom metadata
  })
  .withFunction(/* ... */)
.endService()
```

### Service Naming

Format: `plugin:{plugin-id}:{service-name}`

Example: `plugin:my-plugin:monitoring`

## .onReady()

Lifecycle hook called when plugin loads.

```typescript
.onReady(async (ctx, cleanup) => {
  // Initialization code
  const subscription = await ctx.majk.eventBus.subscribeAll(handler);

  // Register cleanup (called on unload)
  cleanup(() => {
    subscription.unsubscribe();
  });
})
```

## Generated React Hooks

Auto-generated from functions in `ui/src/generated/hooks.ts`.

### Query Hooks (Auto-Fetch)

```typescript
const { data, error, loading, refetch } = useHealth(
  input?,                          // Optional input parameters
  options?: {
    enabled?: boolean,             // Only fetch if true (default: true)
    refetchInterval?: number       // Auto-refetch interval in ms
  }
);
```

### Mutation Hooks (Manual)

```typescript
const { mutate, data, error, loading } = useClearEvents();

// Call mutate manually
await mutate(input);
```

## Testing

### Function Testing

```typescript
import { test, invoke, mock } from '@majkapp/plugin-test';
import assert from 'assert';

test('test name', async () => {
  const context = mock()
    .storage({ key: 'value' })
    .withMajkData({
      conversations: [{ id: 'c1', title: 'Test' }],
      todos: [{ id: 't1', status: 'pending' }],
      projects: [{ id: 'p1', name: 'Project' }]
    })
    .build();

  const result = await invoke('functionName', { input }, { context });

  assert.strictEqual(result.expected, 'value');
});
```

### UI Testing

```typescript
import { test, bot, mock } from '@majkapp/plugin-test';
import assert from 'assert';

test('test name', async () => {
  const context = mock()
    .withMockHandler('health', async () => ({
      status: 'ok',
      timestamp: new Date().toISOString()
    }))
    .build();

  const b = await bot(context);
  await b.goto('/plugin-screens/my-plugin/main');

  await b.waitForSelector('[data-majk-id="element"][data-majk-state="populated"]');

  const text = await b.getText('[data-majk-id="element"]');
  assert.ok(text.includes('expected'));

  await b.screenshot('test.png');
  await b.close();
});
```

## data-majk-* Attributes

### data-majk-id

Identifies elements for testing.

```typescript
<button data-majk-id="saveButton">Save</button>
<div data-majk-id="totalCount">{count}</div>
```

### data-majk-state

Tracks UI state for testing.

```typescript
<div
  data-majk-id="container"
  data-majk-state={loading ? 'loading' : data ? 'populated' : 'empty'}
>
  {/* content */}
</div>
```

Common states: `loading`, `empty`, `populated`, `error`, `idle`, `processing`

## JSON Schema Types

```typescript
// String
{ type: 'string' }
{ type: 'string', enum: ['value1', 'value2'] }
{ type: 'string', format: 'date-time' }

// Number
{ type: 'number' }
{ type: 'number', minimum: 0, maximum: 100 }

// Boolean
{ type: 'boolean' }

// Object
{
  type: 'object',
  properties: {
    field1: { type: 'string' },
    field2: { type: 'number' }
  },
  required: ['field1'],
  additionalProperties: false
}

// Array
{
  type: 'array',
  items: { type: 'string' }
}

// Array of objects
{
  type: 'array',
  items: {
    type: 'object',
    properties: { /* ... */ }
  }
}
```

## Common Patterns

### uiLog Function

```typescript
.function('uiLog', {
  description: 'Log messages from UI for debugging',
  input: {
    type: 'object',
    properties: {
      level: { type: 'string', enum: ['debug', 'info', 'warn', 'error'] },
      component: { type: 'string' },
      message: { type: 'string' },
      data: { type: 'object' }
    },
    required: ['level', 'component', 'message']
  },
  output: {
    type: 'object',
    properties: { success: { type: 'boolean' } }
  },
  handler: async (input, ctx) => {
    ctx.logger[input.level](`[UI:${input.component}] ${input.message}`, input.data);
    return { success: true };
  }
})
```

### Health Check Function

```typescript
.function('health', {
  description: 'Check plugin health status',
  input: {
    type: 'object',
    properties: {},
    additionalProperties: false
  },
  output: {
    type: 'object',
    properties: {
      status: { type: 'string', enum: ['ok', 'error'] },
      timestamp: { type: 'string', format: 'date-time' }
    },
    required: ['status', 'timestamp']
  },
  handler: async (_, ctx) => ({
    status: 'ok',
    timestamp: new Date().toISOString()
  })
})
```

### Event Bus Subscription

```typescript
.onReady(async (ctx, cleanup) => {
  const subscription = await ctx.majk.eventBus.subscribeAll((event) => {
    ctx.logger.info('Event', {
      entityType: event.entityType,
      eventType: event.type,
      entityId: event.entity?.id
    });
  });

  cleanup(() => {
    subscription.unsubscribe();
  });
})
```

## Next Steps

Run `npx @majkapp/plugin-kit` - Quick start guide
Run `npx @majkapp/plugin-kit --functions` - Function patterns
Run `npx @majkapp/plugin-kit --screens` - Screen configuration
Run `npx @majkapp/plugin-kit --hooks` - Generated hooks
Run `npx @majkapp/plugin-kit --context` - Context API
Run `npx @majkapp/plugin-kit --services` - Service grouping
Run `npx @majkapp/plugin-kit --lifecycle` - Lifecycle hooks
Run `npx @majkapp/plugin-kit --testing` - Testing guide
Run `npx @majkapp/plugin-kit --config` - Project configuration
Run `npx @majkapp/plugin-kit --full` - Complete reference
