@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
npm install -D @helix-agents/skill-cliThe helix-skills CLI
One command — sync — with an optional --check mode:
# 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| Flag | Default | Description |
|---|---|---|
--manifest | helix.skills.json | Path to the manifest. |
--out | src/skills.generated.ts | Where to write the generated module. |
--lock | helix.skills.lock | Where to write the lockfile. |
--check | false | Verify-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.
{
"skills": {
"hyperframes": {
"type": "git",
"url": "https://github.com/heygen-com/hyperframes.git",
"version": "0.6.70",
"include": ["hyperframes", "hyperframes-media"]
}
}
}SkillSourceSpec
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[] }.
| Field | Applies to | Notes |
|---|---|---|
version | both | Resolves to the tag v<version> (e.g. 0.6.70 → v0.6.70). |
ref | both | A branch/tag to fetch. Overrides version. |
sha | both | A commit sha to fetch. Highest precedence (sha > ref > v<version>). |
skillsDir | git | Directory scanned for skills. Default skills. |
plugin | claude-marketplace | The plugin name within the marketplace (marketplace.json → plugin → skills). |
include | both | Allow-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
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;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).
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
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;
}| Option | Type | Default | Description |
|---|---|---|---|
manifest | SkillsManifest | — | The parsed manifest. |
outFile | string | — | Path for the generated .ts module. |
lockFile | string | — | Path for the lockfile. |
sources | { git?: SkillSource; 'claude-marketplace'?: SkillSource } | GitRepoSource / ClaudeMarketplaceSource | Override resolvers (e.g. fixtures in tests). |
check | boolean | false | Verify-only; no writes. |
BakeResult
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.
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. Theurlis recorded verbatim in the committed lockfile, so never put auser:token@…in it — use public URLs or ambient git auth.
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/.
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.
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:
// 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.
interface Lockfile {
lockfileVersion: 1;
skills: Record<
string,
{ source: string; resolved: string; integrity: string } // integrity: `sha256-<hex>`
>;
}{
"lockfileVersion": 1,
"skills": {
"hyperframes": {
"source": "git:https://github.com/heygen-com/hyperframes.git",
"resolved": "0.6.70",
"integrity": "sha256-…"
}
}
}Helpers:
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.
Recommended workflow
- Author
helix.skills.jsonwith your pinned remote sources. npx helix-skills syncto bakesrc/skills.generated.ts+helix.skills.lock.- Gitignore
src/skills.generated.ts; commithelix.skills.lock. - Run
npx helix-skills sync --checkin CI so manifest/lockfile drift fails the build. import { skills } from './src/skills.generated'and pass todefineAgent({ skills }).
See also
- Skills guide → Loading remote skill packages — the build-time bake model.
@helix-agents/coreSkills reference —SkillDefinition,SkillMetadata,inCodeSkillProvider.@helix-agents/skill-fsreference — the runtime filesystem provider (Node-only).