Skip to content

@helix-agents/skill-cli

Build-time CLI that bakes remote skill packages into a bundled in-code provider. It resolves remote sources (git repos, Claude plugin marketplaces) pinned at a version, fetches the selected skills, and emits a generated TypeScript module exporting SkillDefinition[] (consumed by inCodeSkillProvider) plus a committed lockfile. Node-only, build-time — never imported by your agent at runtime; the baked output runs everywhere, including Cloudflare Workers.

For the conceptual overview and the build-time bake model, see the Skills guide.

Installation

bash
npm install -D @helix-agents/skill-cli

The helix-skills CLI

One command — sync — with an optional --check mode:

bash
# Resolve + bake → writes the generated module and the lockfile
npx helix-skills sync

# CI mode: don't write; exit non-zero if the lockfile would change
npx helix-skills sync --check
FlagDefaultDescription
--manifesthelix.skills.jsonPath to the manifest.
--outsrc/skills.generated.tsWhere to write the generated module.
--lockhelix.skills.lockWhere to write the lockfile.
--checkfalseVerify-only; fail if the lockfile is stale.

Exit codes: 0 success, 1 a bake/check failure (e.g. a stale lockfile under --check), 2 a usage/manifest error.

The manifest — helix.skills.json

A Zod-validated map of local name → pinned remote source. Each entry is a SkillSourceSpec plus an optional include allow-list.

json
{
  "skills": {
    "hyperframes": {
      "type": "git",
      "url": "https://github.com/heygen-com/hyperframes.git",
      "version": "0.6.70",
      "include": ["hyperframes", "hyperframes-media"]
    }
  }
}

SkillSourceSpec

typescript
type SkillSourceSpec =
  | {
      type: 'git';
      url: string;
      ref?: string;
      sha?: string;
      version?: string;
      skillsDir?: string; // default 'skills'
    }
  | {
      type: 'claude-marketplace';
      url: string; // the marketplace repo
      plugin: string;
      ref?: string;
      sha?: string;
      version?: string;
    };

A manifest entry is SkillSourceSpec & { include?: string[] }.

FieldApplies toNotes
versionbothResolves to the tag v<version> (e.g. 0.6.70v0.6.70).
refbothA branch/tag to fetch. Overrides version.
shabothA commit sha to fetch. Highest precedence (sha > ref > v<version>).
skillsDirgitDirectory scanned for skills. Default skills.
pluginclaude-marketplaceThe plugin name within the marketplace (marketplace.json → plugin → skills).
includebothAllow-list of skill names to bake. Omitted → all discovered skills bake.

claude-marketplace plugin source kinds supported in v1: relative ("./"), github, git-subdir, and url. An npm plugin source throws "not supported in v1".

parseManifest

typescript
import { parseManifest } from '@helix-agents/skill-cli';

const parsed = parseManifest(JSON.parse(rawJson));
if (!parsed.ok) throw new Error(parsed.error);
const manifest: SkillsManifest = parsed.manifest;
typescript
function parseManifest(
  raw: unknown
): { ok: true; manifest: SkillsManifest } | { ok: false; error: string };

manifestSchema (the Zod schema for the whole manifest) is also exported, along with manifestEntrySchema / ManifestEntry — the per-entry schema/type. A ManifestEntry is a single SkillSourceSpec plus an optional include allow-list (i.e. one value of the manifest's skills map); manifestSchema validates a { skills: Record<string, ManifestEntry> } object.

bakeSkills

The programmatic entry point — resolves the manifest's sources, fetches the chosen skills, and writes the generated module + lockfile (or, under check, verifies the lockfile is up to date without writing).

typescript
import { bakeSkills } from '@helix-agents/skill-cli';

const result = await bakeSkills({
  manifest,
  outFile: 'src/skills.generated.ts',
  lockFile: 'helix.skills.lock',
});
if (!result.ok) throw new Error(result.error);

BakeOptions

typescript
interface BakeOptions {
  manifest: SkillsManifest;
  outFile: string;
  lockFile: string;
  /** Override the source resolvers (tests inject fixtures). */
  sources?: { git?: SkillSource; 'claude-marketplace'?: SkillSource };
  /** --check: don't write; succeed iff the lockfile would be unchanged. */
  check?: boolean;
}
OptionTypeDefaultDescription
manifestSkillsManifestThe parsed manifest.
outFilestringPath for the generated .ts module.
lockFilestringPath for the lockfile.
sources{ git?: SkillSource; 'claude-marketplace'?: SkillSource }GitRepoSource / ClaudeMarketplaceSourceOverride resolvers (e.g. fixtures in tests).
checkbooleanfalseVerify-only; no writes.

BakeResult

typescript
type BakeResult = { ok: true } | { ok: false; error: string };

Sources

A SkillSource resolves a SkillSourceSpec to a ResolvedSkillSource — a materialized temp dir exposing the skills it found.

typescript
interface SkillSource {
  resolve(spec: SkillSourceSpec): Promise<ResolvedSkillSource>;
}

interface ResolvedSkillSource {
  /** Pinned identity for the lockfile (concrete resolved ref/version). */
  readonly pinned: { source: string; version: string };
  listSkills(): SkillMetadata[]; // Level-1 metadata
  getBody(name: string): string; // full body
  listResources(name: string): string[]; // relative resource paths (no SKILL.md)
  readResource(name: string, path: string): string; // a resource file's content
}

GitRepoSource

Resolves type: "git" entries. Fetches the repo via degit at the pinned ref (sha > ref > v<version>) and walks skillsDir (default skills) for SKILL.md directories.

degit supports GitHub / GitLab / Sourcehut / BitBucket HTTPS repos — it does not support arbitrary/self-hosted git remotes or file:// URLs.

Don't embed credentials in url. The url is recorded verbatim in the committed lockfile, so never put a user:token@… in it — use public URLs or ambient git auth.

typescript
import { GitRepoSource } from '@helix-agents/skill-cli';

const source = new GitRepoSource();
const resolved = await source.resolve({
  type: 'git',
  url: 'https://github.com/heygen-com/hyperframes.git',
  version: '0.6.70',
});

A materialize option (GitRepoSourceOptions) lets tests inject a local dir in place of the degit fetch.

ClaudeMarketplaceSource

Resolves type: "claude-marketplace" entries: fetches the marketplace repo, reads .claude-plugin/marketplace.json, finds the named plugin, resolves the plugin's own root (relative / github / git-subdir / url), reads its optional .claude-plugin/plugin.json for the version, and walks skills/.

typescript
import { ClaudeMarketplaceSource } from '@helix-agents/skill-cli';

An npm plugin source throws "not supported in v1".

codegen — generateSkillsModule

Emits the generated TypeScript module from a list of BakedSkills. Each entry is wrapped in defineSkill(...) and bodies/resources are JSON-escaped into valid TS string literals; skills and resource keys are deterministically sorted for a byte-stable diff.

typescript
import { generateSkillsModule, type BakedSkill } from '@helix-agents/skill-cli';

interface BakedSkill {
  name: string;
  description: string;
  body: string;
  resources: Record<string, string>;
  // Optional SKILL.md frontmatter fields, carried through from the source.
  license?: string;
  compatibility?: string;
  metadata?: Record<string, string>;
  allowedTools?: string[];
}

const source: string = generateSkillsModule(bakedSkills);

The output:

typescript
// GENERATED by @helix-agents/skill-cli — do not edit. Re-run `helix-skills sync`.
import { defineSkill, type SkillDefinition } from '@helix-agents/core';

export const skills: SkillDefinition[] = [
  defineSkill({
    name: 'hyperframes',
    description: '…',
    body: '…',
  }),
];

The lockfile — helix.skills.lock

A stable, sorted JSON file recording, per manifest-entry local name, the pinned source, the resolved version, and a sha256 integrity hash over that entry's selected skills' raw files (one integrity hash per manifest entry). Commit it; gitignore the generated .ts.

typescript
interface Lockfile {
  lockfileVersion: 1;
  skills: Record<
    string,
    { source: string; resolved: string; integrity: string } // integrity: `sha256-<hex>`
  >;
}
json
{
  "lockfileVersion": 1,
  "skills": {
    "hyperframes": {
      "source": "git:https://github.com/heygen-com/hyperframes.git",
      "resolved": "0.6.70",
      "integrity": "sha256-…"
    }
  }
}

Helpers:

typescript
import { parseLockfile, serializeLockfile, skillIntegrity } from '@helix-agents/skill-cli';

const text = serializeLockfile(lock); // byte-stable, sorted keys
const parsed = parseLockfile(text); // { ok: true; lockfile } | { ok: false; error }
const hash = skillIntegrity({ 'pdf/SKILL.md': '…' }); // `sha256-<hex>`

skillIntegrity is a deterministic, order-independent sha256 over a path→content map. The lockfile records one integrity hash per manifest entry (over its selected skills' raw files, including each SKILL.md's frontmatter), recorded for reproducibility. Security in v1 is disclose-only — there is no signature verification.

  1. Author helix.skills.json with your pinned remote sources.
  2. npx helix-skills sync to bake src/skills.generated.ts + helix.skills.lock.
  3. Gitignore src/skills.generated.ts; commit helix.skills.lock.
  4. Run npx helix-skills sync --check in CI so manifest/lockfile drift fails the build.
  5. import { skills } from './src/skills.generated' and pass to defineAgent({ skills }).

See also

Released under the MIT License.