npm package: @linwheel/sdk
Description: TypeScript SDK for the LinWheel content engine API
Latest version: 0.1.0

# @linwheel/sdk

TypeScript SDK for the [LinWheel](https://www.linwheel.io) content engine API. Analyze, reshape, refine, and schedule LinkedIn content programmatically.

Zero runtime dependencies. Uses native `fetch`. Ships ESM + CJS with full type declarations.

## Install

```bash
npm install @linwheel/sdk
```

## Quick Start

```typescript
import { LinWheel } from "@linwheel/sdk";

const lw = new LinWheel({ apiKey: "lw_sk_..." });

// Analyze content for LinkedIn potential
const analysis = await lw.analyze({ text: "Today I shipped a new SDK..." });
// → { linkedinFit: { score: 8 }, suggestedAngles: [...], recommendedPostCount: 3 }

// Reshape into angle-specific posts
const { posts } = await lw.reshape({
  text: "Today I shipped a new SDK...",
  angles: ["field_note", "contrarian"],
  saveDrafts: true,
});

// Refine a draft
const { refined } = await lw.refine({
  text: posts[0].text,
  intensity: "medium",
});

// Approve and schedule
await lw.posts.approve(postId, true);
await lw.posts.schedule(postId, {
  scheduledAt: "2026-02-24T09:00:00Z",
  autoPublish: true,
});
```

## API Reference

### Content Processing

```typescript
// Analyze text for LinkedIn potential — topics, angles, fit score
const analysis = await lw.analyze({ text, context? });

// Reshape content through 7 angle lenses
const result = await lw.reshape({
  text,
  angles: ["field_note", "contrarian", "demystification", ...],
  preEdit?: boolean,       // light-edit input first
  instructions?: string,   // custom instructions
  saveDrafts?: boolean,    // persist as drafts
});

// Refine with parameterized intensity
const result = await lw.refine({
  text,
  intensity?: "light" | "medium" | "heavy",  // default: "medium"
  instructions?: string,
  postType?: string,       // target angle for tone
  saveDraft?: boolean,
});

// Split long content into a post series
const result = await lw.split({
  text,
  maxPosts?: number,       // 2-10, default 5
  instructions?: string,
  saveDrafts?: boolean,
});
```

### Drafting

```typescript
// Create a manual draft
const draft = await lw.draft({
  fullText: "Your post content...",
  hook?: string,
  postType?: string,        // default: "field_note"
  approved?: boolean,
  autoPublish?: boolean,
  scheduledAt?: string,     // ISO 8601
});

// Create a draft with image + carousel in one call
const bundle = await lw.bundle({
  fullText: "Your post content...",
  imageHeadlineText?: string,
  imageStylePreset?: "typographic_minimal" | "gradient_text" | "dark_mode" | "accent_bar" | "abstract_shapes",
  carouselSlides?: [{ headlineText: string, caption?: string }],
});
```

### Post Management

```typescript
// List posts with filters
const { posts } = await lw.posts.list({
  approved?: boolean,
  scheduled?: boolean,
  published?: boolean,
  type?: string,
  limit?: number,
  offset?: number,
});

// Get, update, approve, schedule
const post = await lw.posts.get(postId);
await lw.posts.update(postId, { fullText: "..." });
await lw.posts.approve(postId, true);
await lw.posts.schedule(postId, { scheduledAt: "2026-02-24T09:00:00Z" });

// Generate visuals
await lw.posts.image(postId, { headlineText: "...", stylePreset: "dark_mode" });
await lw.posts.carousel(postId, { slides: [...], stylePreset: "accent_bar" });
```

### Voice Profiles

Voice profiles inject your writing style into all content generation. Provide writing samples and LinWheel matches your voice.

```typescript
// Create a voice profile from your writing
const { profile } = await lw.voiceProfiles.create({
  name: "My Writing Voice",
  description: "Technical, direct, slightly irreverent",
  samples: [article1, article2, article3],
  isActive: true,
});

// List all profiles
const { profiles, activeProfileId } = await lw.voiceProfiles.list();

// Switch active profile
await lw.voiceProfiles.activate(profileId);

// Delete a profile
await lw.voiceProfiles.delete(profileId);
```

## Configuration

```typescript
const lw = new LinWheel({
  apiKey: "lw_sk_...",                           // required
  baseUrl: "https://www.linwheel.io",            // default
  signingSecret: "your-hmac-secret",             // optional, for request signing
  timeoutMs: 60_000,                             // default: 60s
});
```

### HMAC Request Signing

If your API key has a signing secret, the SDK automatically signs every request with `X-LW-Signature`:

```
X-LW-Signature: t=<unix-seconds>,v1=<hmac-sha256-hex>
```

Canonical payload: `<timestamp>.<METHOD>.<path>.<body-sha256>`

## Error Handling

```typescript
import { LinWheelApiError, LinWheelAuthError } from "@linwheel/sdk";

try {
  await lw.analyze({ text: "..." });
} catch (e) {
  if (e instanceof LinWheelAuthError) {
    // 401 — invalid or missing API key
    console.error(e.message, e.responseBody);
  } else if (e instanceof LinWheelApiError) {
    // 4xx/5xx
    console.error(e.statusCode, e.message, e.responseBody);
  }
}
```

## The 7 Content Angles

| Angle | What it does |
|-------|-------------|
| `contrarian` | Challenge conventional wisdom |
| `field_note` | Share a specific observation from experience |
| `demystification` | Break down something complex |
| `identity_validation` | Make the reader feel seen |
| `provocateur` | Be deliberately provocative |
| `synthesizer` | Connect dots across domains |
| `curious_cat` | Ask genuine questions |

## License

MIT