/ documentation

Schema

Root and source fields in maleta.json schema 1, normalization rules, and core validation messages.

The maleta.json file follows a single, explicit schema, currently version 1. Selection fields live at the top level: skills, plugins, targetTool and targetOs do not live inside a nested object. Every field is read by parseMaleta in packages/core/src/maleta.ts.

#Complete schema 1

The document below is a valid example with builtin, GitHub, and inline sources. serializeMaleta writes the normalized form shown here:

Valid maleta.json using schema 1
json
{
  "schemaVersion": 1,
  "id": "shared-fixture",
  "slug": "shared-fixture",
  "name": "Shared fixture",
  "description": "Builtin, GitHub, and inline selection used by site and CLI contract tests.",
  "createdAt": "2026-01-01T00:00:00.000Z",
  "updatedAt": "2026-01-01T00:00:00.000Z",
  "skills": [
    {
      "id": "builtin:design-taste-frontend",
      "name": "design-taste-frontend",
      "source": { "type": "builtin", "name": "design-taste-frontend" }
    },
    {
      "id": "github:acme%2Fskills:skills%2Freviewer%2FSKILL.md@v1.2.0",
      "name": "reviewer",
      "source": { "type": "github", "repo": "acme/skills", "path": "skills/reviewer/SKILL.md", "ref": "v1.2.0" },
      "description": "Review code before shipping."
    },
    {
      "id": "inline:release-checklist",
      "name": "release-checklist",
      "source": {
        "type": "inline",
        "name": "release-checklist",
        "files": [
          { "path": "SKILL.md", "content": "# Release checklist\n\nRun tests before shipping.\n" }
        ]
      }
    }
  ],
  "plugins": ["frontend-design@claude-plugins-official"],
  "targetTool": "claude",
  "targetOs": "unix"
}

#Root fields

schemaVersion
1required

Literal 1. Any other value is rejected with Unsupported Maleta format — there is no migration between versions.

id
stringrequired

Maleta identifier, up to 128 characters. It is not derived from slug and does not need to be unique across files.

slug
stringrequired

Up to 96 characters, in the format palavra or palavra-palavra: Unicode letters and numbers only, with segments separated by a single hyphen.

name
stringrequired

Human-readable Maleta name, up to 80 characters. This is not the skill name: each skill has its own name, which defines the destination directory.

description
stringoptional

Free-form text up to 600 characters. It accepts line breaks; the other text fields do not.

createdAt
stringrequired

A string accepted by Date.parse, up to 64 characters. Usually ISO 8601.

updatedAt
stringrequired

The same rule as createdAt. maleta init writes the same instant to both fields.

skills
arrayrequired

Up to 500 entries, with no duplicate id values. Each entry is a { id, name, source, description? } object or a string, which is expanded into a builtin source with the same name. A skill's name must match [A-Za-z0-9._-]+ and must not be ., .., or contain .. — for the string shorthand too. Two skills that would land in the same destination directory are rejected with Skill destination collision '<name>' between '<id>' and '<id>'.

plugins
string[]required

Up to 200 identifiers, each up to 200 characters, with no duplicates. Use [] for an empty list; the field is required in the input file.

targetTool
"all" | "claude" | "codex" | "agents"required

The selection's default target tool. This value is used when --tool is omitted at runtime.

targetOs
"windows" | "unix"required

Target operating system. It must match the host when running install and sync.

#source: union discriminated by type

Each item in skills[] points to a source. The id field provided in the file is discarded and recalculated by skillId(source): builtin:<name>, inline:<name>, or github:<repo>:<path>[@<ref>], with percent-encoded components.

#{ "type": "builtin", "name": "…" }

type
"builtin"required

A skill bundled with the local assets. This is the form used by a bare string in skills[], which is expanded into a builtin source with the same text.

name
stringrequired

It must be identical to the skill's name; any mismatch produces Source for skill "<x>" does not match its name. It is also the destination directory name and must match [A-Za-z0-9._-]+, must not be . or .., and must not contain ...

#{ "type": "github", "repo": "…", "path": "…", "ref": "…" }

type
"github"required

A skill resolved from a public repository at the declared revision.

repo
stringrequired

Accepts owner/name or an https://github.com/owner/name[.git] URL, which is normalized to owner/name. The result must match [A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+.

path
stringrequired

Relative path inside the repository, up to 240 characters. Rejects \, a leading slash, //, the segments . and .., empty segments, and any character outside [A-Za-z0-9._-]. A path ending in SKILL.md is reduced to its parent directory.

ref
stringoptional

Branch, tag, or SHA, up to 160 characters. Accepts [A-Za-z0-9._/-] and rejects \, //, and ... When omitted, the GitHub API resolves the repository's default branch.

#{ "type": "inline", "name": "…", "files": […] }

type
"inline"required

A skill whose file content travels inside maleta.json. The CLI writes the content as opaque UTF-8 data and never executes it.

name
stringrequired

It must be identical to the skill's name; a mismatch produces Source for skill "<name>" does not match its name. The source identity is inline:<name>.

files
{ path: string; content: string }[]required

Contains 1 to 200 entries. Paths are safe relative POSIX paths of at most 240 characters: no leading slash, backslash, empty segment, . or .. segment, and only [A-Za-z0-9._-] in each segment. Paths must be unique case-insensitively, and an exact root SKILL.md is required. Normalization sorts entries deterministically by path.

files[].content
stringrequired

Preserved without trimming. Tabs, line feeds, and carriage returns are allowed; other ASCII control characters and DEL are rejected.

#Normalization

  • Skills are sorted by id and plugins alphabetically; duplicate id values and plugin identifiers are discarded.
  • Text fields except inline file content are passed through trim; fields containing forbidden control characters are rejected. description and inline content may contain line breaks.
  • Unknown top-level keys are silently discarded; a root selection key is an explicit error.
  • The file is read only as valid JSON up to 1 MiB in UTF-8 bytes. Above that limit, Maleta file is too large.
  • serializeMaleta re-emits the document with JSON.stringify(doc, null, 2) and a final newline, so parseMaleta(serializeMaleta(m)) preserves the fields and canonical order.

#Core validation messages

For maleta validate, the error is printed as [error] <caminho>: <mensagem> and the process exits with code 1. These are the possible messages:

Core validation messages
MessageCause
Invalid Maleta JSONThe file content is not parseable JSON.
Maleta file is too largeThe input string exceeds 1 MiB in UTF-8 bytes.
Unsupported Maleta formatThe object is not a record or schemaVersion is not 1. There is no migration between versions.
Invalid Maleta format: selection fields must be at the top levelA selection key exists at the document root.
Maleta must expose skills, plugins, targetTool, and targetOs at the top levelAt least one of slug, skills, plugins, targetTool, and targetOs is missing from the root.
Invalid skill pathsource.path violates the safe relative path rules.
Invalid skill referenceref contains a forbidden character or exceeds 160 characters.
Invalid GitHub repositoryrepo does not normalize to owner/name.
Source for skill "<name>" does not match its nameThe name of a builtin or inline source does not match the skill's name.
Invalid source for skill "<name>"The source is not an object or its type is not builtin, github, or inline.
Invalid source name for "<name>"An inline or builtin source.name is missing, empty, over 120 characters, or contains a control character.
Invalid files for skill "<name>"source.files is not an array or contains fewer than 1 or more than 200 entries.
Invalid file for skill "<name>"An item in source.files is not an object.
Invalid file path for skill "<name>"An inline file path violates the safe relative POSIX path rules or exceeds 240 characters.
Invalid file content for skill "<name>"An inline file's content is not a string or contains a forbidden control character.
Duplicate file path '<path>' in skill "<name>"Two inline paths are equal case-insensitively.
Skill "<name>" is missing a root SKILL.md fileNo inline file has the exact root path SKILL.md.
Invalid target toolThe targetTool value is outside the four-value enum.
Invalid operating systemThe targetOs value is neither windows nor unix.
Invalid Maleta slugThe slug field does not match hyphen-separated letters and numbers, or exceeds 96 characters.
Invalid skill name '<x>'A skill name (or source.name) fails [A-Za-z0-9._-]+ or is ./../contains ... The string shorthand is validated the same way.
Invalid skill name '<x>' for WindowsUnder targetOs: "windows", the name ends with a dot or space, or is a DOS device name such as CON or NUL.
Skill destination collision '<name>' between '<id>' and '<id>'Two skills would be written to the same destination directory. The comparison is case-insensitive on Windows.
file is UTF-16; save it as UTF-8The --file bytes carry a UTF-16 BOM or a UTF-16 NUL pattern. A UTF-8 BOM is stripped and accepted.

Privacy preferences

Choose what this origin may measure. You can change it here at any time.

Essential storage

This browser always keeps your theme, the Maletas you save here and this choice so the site works. They stay on this origin.

Optional analytics

Maleta.dev measures aggregate traffic and real-user performance only after you allow it.

Current choice: not made yet, so measurement stays off.

Privacy policy