#Usage and options
maleta install --file ./maleta.json --tool claudemaleta install --file ./maleta.json --tool claude --dry-runWithout --file, it reads maleta.json relative to the current directory. Without --tool, it uses targetTool from the file. Accepted values are all, claude, codex, and agents. --dry-run is available only for install and sync.
#Skills and destinations
- A builtin source is looked up in the local assets at
claude/skills/<name>. Thesharedfolder is added automatically when a valid builtin skill exists. - A GitHub source uses the declared repository, path, and optional ref. Content is resolved through the GitHub API from the declared public repository and written as bytes; it is not executed. Each resolve prints
[info] resolving GitHub skill <name> from <identity>, and when a mixed document fails on a GitHub skill the error ends with; local skills were not installed eitherso it never half-installs. - An inline source reads its files from
maleta.jsonand therefore makes no GitHub request. Content is written as opaque UTF-8 data and never executed. A changed write reports[ok] install inline:<name> -> <dest>. - The destination for
claudeis~/.claude/skills/<skill.name>/. The destinations forcodexandagentsare~/.agents/skills/<skill.name>/.allexpands to both directories (Codex and Agents share the second one).
#Plugins
Plugins are applied by the Claude command. If the selection runs with --tool codex or --tool agents, each plugin is skipped with an incompatibility message. The plan can add the marketplace with claude plugin marketplace add <repo> and install with claude plugin install <id>.
[skip] plugin '<id>' is Claude-only and is unsupported for --tool <tool>[skip] plugin '<id>' is not present in the local plugin manifest; not installed[ok] plugin '<id>' installed
#Safe, idempotent writes
The operation is additive: it copies the existing tree to a staging directory and overwrites only files from the selection. It does not prune and preserves unmanaged files. Identical bytes are not rewritten and appear as [ok] <label> already aligned -> <dest>. Replacement uses transactional staging, backup, and rollback; temporary directories are named .maleta-stage-* and <stage>.old during the operation.
#Destination safeguards
A skill name that is not a safe directory name is rejected by document validation, before any plan is built: Invalid skill name '<x>'. At write time, a symlinked destination is refused with an explanation and the MALETA_CLI_HOME workaround; a destination that is not a directory reports destination is not a directory: <path>; a staged file that is not a regular file reports destination is not a regular file: <dest>; and a read-only destination reports permission denied writing to <dir>; check its ownership and permissions.
#Dry run
With --dry-run, the CLI makes no network requests and writes nothing to disk. An inline action uses its source identity as the label: [dry-run] would install inline:<name> -> <dest>. The general materialization line is:
[dry-run] would install <label> -> <dest>[dry-run] claude plugin marketplace add <repo>
[dry-run] claude plugin install <id>An empty selection prints [warn] no skills or plugins selected; edit the selected Maleta file or export one from https://app.maleta.dev before the usual install: no relevant changes footer, including with --dry-run. Otherwise, with --dry-run and at least one materialization action planned, no footer is printed. With no relevant changes—no files changed and no plugin commands run—the footer is install: no relevant changes; when the local selection is applied, the footer is install: applied local selection.
#Local ownership record
After a run, install and sync record what they wrote for that exact absolute document path in <home>/.maleta/local-state.json (mode 0600, localStateVersion: 1). The record lets a later run tell its own files from a file you edited: a file that differs and was not last written by the CLI is kept with [warn] kept <path>: changed locally; delete it to take the new version, while files the CLI wrote are still updated and adopted files stay adopted. It also lets maleta prune remove a skill, including an inline:<name> resource, once the recorded document stops declaring it.
A skill whose destination already contains a symbolic link is not written through: the CLI prints [skip] <label> -> <dest>: existing symbolic link at <path>; remove it and rerun and continues with the other skills.
#How it differs from sync
| Aspect | install | sync |
|---|---|---|
| Plugin pre-check | None. | Runs claude plugin list before installation. |
| Plugin already installed | Reinstalls it. | Skips it with [ok] plugin '<id>' already installed. |
| Changed file | [ok] install <label> -> <dest> | [ok] sync <label> -> <dest> |
| Footer | install: no relevant changes or install: applied local selection | sync: no relevant changes or sync: applied local selection |
#Exit codes
| Code | Condition |
|---|---|
0 | No failures (`failures === 0`); skipped or unsupported items also result in 0. |
1 | Document, plan, GitHub resolution, limit, collision, staging/transfer, or plugin error. |
2 | Invalid command, option, or flag value. |
4 | The file's targetOs differs from the host: [error] targetOs '<x>' does not match the local host '<y>'; refusing install. |