Converting
You probably already have plugins — a Claude Code marketplace, a Codex
project, or a set of SKILL.md files written for Cursor or OpenCode.
tome catalog convert (and its plugin and skill siblings) turns
what you have into native Tome artifacts, reports exactly what it could and
could not convert, and never modifies the original. Convert instead of
rewriting.
What converts
The command names what you want to produce: a whole catalog (catalog), one
plugin (plugin), or a single skill (skill). The source format is
auto-detected; --from overrides detection when it can't decide. --from takes
a closed set of values (validated at parse time — an unknown value is a usage
error, exit 2): the six sources below, plus the claude and agent aliases.
Source (--from) | What it covers |
|---|---|
claude-code (alias claude) | Claude Code marketplaces, plugins, and skills |
codex | Codex projects |
cursor | Native SKILL.md trees written for Cursor |
opencode | Native SKILL.md trees written for OpenCode |
cline | Native SKILL.md trees written for Cline |
agent-skills (alias agent) | The generic Agent Skills SKILL.md layout |
When detection fails, convert exits 83 and asks you to pass --from rather
than guessing.
A marketplace becomes a catalog
The catalog used throughout these docs is a real conversion: the
devrelaicom/midnight-expert Claude Code marketplace — 13 plugins — converted
in one command. Always start with --dry-run, which prints the full plan and
writes nothing:
$ tome catalog convert ~/.claude/plugins/marketplaces/midnight-expert --output ~/catalogs --dry-run
Would convert claude-code catalog `midnight-expert` → `midnight-expert-tome`
[info] convert/catalog-synthesized-field: marketplace has no `description`; synthesizing one
[info] convert/dropped-manifest-field: plugin.json `homepage` is not modelled by Tome; dropping it
[warning] harness-ism/claude-skill-dir: rewrote 6 occurrence(s) of `${CLAUDE_SKILL_DIR}` → `${TOME_SKILL_DIR}`
[warning] harness-ism/claude-plugin-data: rewrote 2 occurrence(s) of `${CLAUDE_PLUGIN_DATA}` → `${TOME_PLUGIN_DATA}`
…
[warning] convert/tool-restriction-dropped: frontmatter `allowed-tools` (a tool restriction) is dropped — Tome does not constrain tools, so dropping it silently broadens capability
[warning] convert/agent-lossy: agent frontmatter `memory` is not modelled by Tome; dropping it (agent conversion is lossy)
…
Dry run: 616 file(s) to …/midnight-expert-tome (128 warning(s), 100 info(s))
(Excerpt — the full plan lists every diagnostic; output paths shortened here.)
Read the warnings before you continue. Each one records a decision convert
made for you: a rewritten variable, a dropped manifest field, a tool
restriction it cannot enforce. When you are satisfied, run the same command
without --dry-run:
$ tome catalog convert ~/.claude/plugins/marketplaces/midnight-expert --output ~/catalogs
Converted claude-code catalog `midnight-expert` → `midnight-expert-tome`
…
Done: 616 file(s) to …/midnight-expert-tome (128 warning(s), 100 info(s))
Then lint the result to see what still needs manual review:
$ tome catalog lint ~/catalogs/midnight-expert-tome
[warning] lint/description-too-long: entry `compact-dev` description is 2557 characters (max 1024) (…/midnight-expert-tome/compact-core/agents/compact-dev.md)
[warning] lint/residual-harness-ism: file-reference injection (`@path`) is not supported — Tome does not inject file contents (…/midnight-expert-tome/midnight-verify/skills/midnight-verify:verify-by-execution/SKILL.md)
…
Summary: 0 error(s), 18 warning(s), 0 info(s)
Zero errors: the catalog works as-is. The 18 warnings are what remains for
manual review — over-long descriptions and harness-isms Tome does not
emulate. Linting covers how to work through them (and what
--autofix can do for you).
You don't have to remember that lint step. When a convert writes files and
surfaces at least one warning, it prints a Next: line with the exact,
copy-pasteable commands to run:
Next: run `tome catalog lint ~/catalogs/midnight-expert-tome --autofix`, then `tome harness use <harness>`
The convert level (catalog/plugin/skill) and the on-disk path convert
just wrote are filled in for you; <harness> is the placeholder you replace.
The bridge is gated: a clean convert (no warnings) needs no lint nudge, and a
--dry-run writes nothing, so the lint target wouldn't exist — in both cases
no Next: line is printed. It is human output only. The --json stream is
unchanged: no Next: line, and it still ends with the type: "result"
summary, so scripts parsing --json are unaffected.
Output location
Convert never modifies the source — it writes a converted copy:
- The copy is named
<source-name>-tomeby default. Override it with the optional positionalNAMEargument (there is no--nameflag — the name is a single positional, consistent across the authoring verbs). - Convert writes the copy under the current directory by default;
--output <dir>selects a different parent. --into <path>places the converted artifact inside an existing Tome artifact instead: a plugin converted--intoa catalog is registered in itstome-catalog.toml; a skill converted--intoa plugin is written to itsskills/directory.--intoand--outputare mutually exclusive.- If the destination already contains files convert wants to write, it refuses
with exit
81.--forceoverwrites the colliding files — only those files, never the whole directory.
Remote sources
The source is a single positional — a local path, an owner/repo shorthand, or
a git URL. Remote sources are fetched as a shallow clone into a temporary
directory and cleaned up on every exit path — success, failure, or --strict
abort. If the source string could be read both ways (a local directory named
owner/repo exists), the local path wins. (convert is single-source by
design — unlike lint, it does not take multiple sources, because its staging
is all-or-nothing.) A source path that is not valid UTF-8 is rejected with a
clear usage error (exit 2).
For tome catalog convert only, --no-fetch (alias --local-only) skips the
marketplace's remote-source plugins: they're warned and skipped instead of
fetched. The source argument itself may still be remote. The flag lives on
catalog convert alone — passing it to plugin convert or skill convert is a
usage error (a single plugin or skill has no remote-plugin fan-out to skip).
What gets rewritten
Claude Code bodies use variables that Tome does not support directly. Convert rewrites them to their Tome equivalents and reports every occurrence:
| Claude Code | Tome |
|---|---|
${CLAUDE_SKILL_DIR} | ${TOME_SKILL_DIR} |
${CLAUDE_PLUGIN_DATA} | ${TOME_PLUGIN_DATA} |
${CLAUDE_PLUGIN_ROOT} | ${TOME_PLUGIN_DIR} |
${CLAUDE_PROJECT_DIR} | ${TOME_PROJECT_DIR} |
Legacy positional arguments ($1..$9) are rewritten to Tome's 0-based
argument substitution.
What cannot be converted
Some features Tome cannot represent, and convert reports them instead of
silently dropping them. Hook monitors, LSP servers, themes, output styles, and
the other unsupported components are each reported as a warning that names
exactly what was lost. Two injections can't be rewritten at all —
file-reference injection (@path) and shell-execution injection — because
Tome does not inject file contents or execute commands in bodies; convert
warns and leaves them in place for you to review, and lint keeps flagging
them afterwards as lint/residual-harness-ism.
If you prefer to fail rather than lose anything, pass --strict: any
unsupported feature aborts the conversion with exit 84, writing nothing. The
error names how many blocking findings there were and the distinct rule-ids
behind them, so you can see the whole set in one run rather than fixing them one
at a time.
When a drop is intentional — a plugin that deliberately ships a themes/
directory Tome cannot carry, say — demote that rule with --allow <rule-id> so
it no longer blocks --strict. The flag is repeatable
(--allow convert/unsupported-component --allow convert/agent-lossy); an allowed
rule is still reported as a warning, it just stops aborting the run. Naming a
rule-id that is not blocking (or does not exist) is a harmless no-op. This lets
convert --strict serve as a CI gate that fails on new lossiness while
tolerating the drops you have already reviewed.
Why exit 80 exists
Tome reads exactly one plugin manifest: tome-plugin.toml, parsed strictly.
There is no fallback — a plugin that ships only a legacy
.claude-plugin/plugin.json is not loaded; Tome exits 80 with a message
telling you to run tome plugin convert. One format means you always know
whether a plugin has been converted, and there is no lenient partial parse to
debug.
For authors, the practical consequence: run convert against your own checkout,
review the converted copy, and commit it — replacing the legacy layout — so
your users never see exit 80. Publish the converted tree; see
Distributing. For the manifest itself, see
the authoring overview.
Scripting with --json
--json emits a JSONL stream — one JSON object per line. Each diagnostic
carries a "type": "diagnostic" discriminator, and the run ends with one
"type": "result" summary line:
{"rule":"convert/unsupported-component","severity":"warning","message":"`monitors/` (monitors) is not representable in Tome; dropped from the conversion","file":null,"line":null,"autofixable":false,"type":"diagnostic"}
{"type":"result","harness":"claude-code","level":"plugin","source_name":"demo","final_name":"demo-tome","target":"/out/demo-tome","dry_run":false,"written":42,"errors":0,"warnings":1,"infos":1,"strict_blocked":null}
Every diagnostic line carries the same finding fields as a lint --json
finding — rule, severity, message, file, line, autofixable — with
identical value semantics: file/line are the diagnostic's location (or
null when absent) and autofixable is true when lint --autofix can fix
it mechanically. The only difference is convert's "type": "diagnostic"
discriminator and its JSONL envelope. A script that parses
lint findings can parse convert diagnostic
lines with the same code.
Run the workflow with your agent
Tome includes a bundled meta skill, convert-marketplace, that teaches your
agent this whole workflow: run convert and lint, review the remaining
warnings, and report back for your confirmation before registering anything:
tome meta add convert-marketplace
See Meta skills.
Pitfalls
| Exit | When it happens | What to do |
|---|---|---|
80 | A plugin ships only a legacy plugin.json — Tome won't load it | Run tome plugin convert (or ask the author to) |
81 | The destination already contains files convert would write | --force to overwrite the colliding files, or pick another --output |
83 | Source format detection failed | Pass --from <source> |
84 | --strict found a feature Tome cannot represent | Drop --strict to convert with warnings, or remove the unsupported component |
Full table in the exit code reference.
Next steps
- Linting — work through the warnings, use the verdict in CI.
- Distributing — publish the converted catalog.
- Authoring overview — the structure of the converted layout.