# Library (husk-core)

The CLI is a thin shell over [`@elisym/husk-core`](https://www.npmjs.com/package/@elisym/husk-core), the engine. Use the library directly when you want to embed the server, add authentication, or invoke skills in-process.

```bash
bun add @elisym/husk-core
```

## Serve skills over HTTP

`createFetchHandler` returns a Web-standard `(Request) => Promise<Response>`, so it runs under Bun, Deno, Cloudflare Workers, or a Node adapter.

```ts
import { loadSkills, createFetchHandler } from '@elisym/husk-core';

const { skills, errors } = loadSkills('./skills');
errors.forEach((e) => console.warn(`skipped ${e.dir}: ${e.message}`));

const handler = createFetchHandler({
  skills,
  serviceName: 'My Agent',
  cors: true, // off by default; safe to enable here because `auth` is set
  concurrency: 8, // cap concurrent kernels (0 = unlimited)
  auth: (req) => req.headers.get('authorization') === `Bearer ${process.env.TOKEN}`,
});

Bun.serve({ port: 3000, fetch: handler });
```

### `HuskServerOptions`

| Option           | Default          | Notes                                                                       |
| ---------------- | ---------------- | --------------------------------------------------------------------------- |
| `skills`         | -                | The loaded skills to serve.                                                 |
| `serviceName`    | `HUSK skills`    | Shown on the index page and in OpenAPI.                                     |
| `version`        | `0.1.0`          | Version advertised in the generated OpenAPI spec.                           |
| `cors`           | `false`          | Emit permissive CORS headers (`*`). Opt in for cross-origin browser reads.  |
| `maxBodyBytes`   | `52428800`       | Reject larger request bodies (50 MB).                                       |
| `maxOutputBytes` | `104857600`      | Reject kernel file output above this (single or total, 100 MB).             |
| `maxStreamBytes` | `maxOutputBytes` | Truncate an SSE stream past this many bytes (bounds per-connection memory). |
| `concurrency`    | `0`              | Max concurrent kernel invocations. `0` = unlimited.                         |
| `allowedHosts`   | -                | Allowlist of `Host` hostnames (anti-DNS-rebinding). Unset = any host.       |
| `auth`           | -                | `(req) => boolean \| Promise<boolean>`. Return false → 401.                 |

## Invoke a skill in-process

```ts
import { loadSkill, invokeSkill } from '@elisym/husk-core';

const skill = loadSkill('./skills/uppercase');
const result = await invokeSkill(skill, { text: 'hello' });
console.log(result.ok, result.stdout); // true "HELLO"
await result.cleanup(); // always clean up temp files
```

`invokeSkill` returns a structured `InvokeResult`: `{ ok, stdout, stderr, exitCode, timedOut, outputKind, files, durationMs, cleanup }`.

## API surface

| Export                               | Purpose                                             |
| ------------------------------------ | --------------------------------------------------- |
| `loadSkills(dir)` / `loadSkill(dir)` | Discover and parse skills from disk.                |
| `parseManifest` / `parseFrontmatter` | The SKILL.md parser.                                |
| `invokeSkill(skill, input, opts)`    | Run a kernel; returns an `InvokeResult`.            |
| `kernelErrorMessage(result)`         | A human-readable explanation for a failed result.   |
| `runProcess(cmd, args, opts)`        | The hardened child-process runner.                  |
| `createFetchHandler(options)`        | The HTTP handler (routing, SSE, files, CORS, auth). |
| `generateOpenApi(skills, opts)`      | Build an OpenAPI 3.1 document.                      |
| `toCard(skill)` / `mimeFromPath`     | Small helpers.                                      |

## Types

The package ships full TypeScript types - `Skill`, `SkillManifest`, `InvokeInput`, `InvokeResult`, `SkillCard`, and more. Everything is strict, ESM, and dependency-light (only a YAML parser at runtime).
