Shenron keeps agent configurations aligned across AI coding assistants from one CLI-agnostic source of truth.
Define agents, prompts, slash commands, permissions, and per-agent skill
bindings once in shenron.yaml. Shenron validates that pivot, previews the
native changes, then writes the corresponding Claude Code, Codex, and OpenCode files.
| Capability | Claude Code | Codex | OpenCode |
|---|---|---|---|
| Agents | ~/.claude/agents/<id>.md |
~/.codex/agents/<id>.toml |
agent.<id> in ~/.config/opencode/opencode.json |
| Agent prompts | Markdown body | developer_instructions |
prompts/<id>.md referenced from JSON |
| Slash commands | ~/.claude/commands/<id>.md |
~/.codex/prompts/<id>.md |
command.<id> plus command/<id>.md |
| Permissions | tools and permissionMode |
Sandbox, approvals, and web search | Native permission object |
| Per-agent skills | YAML frontmatter skills |
Instruction hint | Dropped (see docs/SKILLS.md) |
Bootstrap with shenron install |
After OpenCode | After Claude Code | Preferred import source |
Shenron targets Claude Code, Codex, and OpenCode.
Why no
skillsin OpenCode output? OpenCode v1.x does not recognizeskillsas an agent field, so the CLI forwards unknown top-level options to the LLM provider as payload fields. Strict providers (PydanticadditionalProperties: false, e.g. GLM-5.2) reject them with 400Extra inputs are not permitted. Pivotagents[].skillsis therefore not emitted to OpenCode. Claude Code (native frontmatter) and Codex (instruction hint) keep receiving the bindings, and OpenCode agents are expected to reference skills from their prompt instead.
Shenron requires Go 1.24 or newer.
git clone git@github.com:S1933/Shenron.git
cd Shenron
make buildThis produces ./shenron. You can also build it directly:
go build -o shenron ./cmd/shenronShenron manages installed configuration packages. Every command operates on
a package in the store (~/.shenron/packages/ by default); there is no
single-pivot mode.
# Install a local package directory.
./shenron install ./my-package
# See what is installed.
./shenron list
# Preview the package's native changes without writing.
./shenron diff my-package
# Push to every supported target.
./shenron push my-packageTo work with one target only:
./shenron diff my-package --target opencode
./shenron push my-package --target claude-codeThe first time a revision declares permission grants, push requires
--allow-permissions. After a successful push, running diff again
reports No changes for each synchronized target.
| Command | Behavior |
|---|---|
shenron install <source> |
Install a local directory or a remote Git package (public HTTPS, or SSH via git@host:path / ssh://). |
shenron list |
List installed packages, ordered by name. |
shenron update <name> |
Validate and replace an installed snapshot from a new source or ref. |
shenron diff <name> |
Show a package's native diff plus its permission grants and missing skills. |
shenron push <name> |
Generate and atomically write a package's native files, then update its state. |
shenron doctor |
Check tool paths, snapshot-cache integrity, sync state, and pending permission approvals. |
shenron explain <name> --target <tool> |
Preview the native files a package translates into for one target, without writing. |
Common flags:
--store <path>(root, persistent) selects a custom package cache directory (default~/.shenron/packages).install --ref <tag-or-sha>pins the Git revision for HTTPS and SSH sources. SSH sources (git@host:pathorssh://β¦) authenticate through your ssh-agent and verify host keys against~/.ssh/known_hosts; credentials are never read from the URL.update --source <dir-or-url>andupdate --ref <tag-or-sha>replace the installed package's source and revision.diff --target <name>andpush --target <name>selectclaude-code,codex, oropencode.push --forceoverwrites native files that changed after the last push.push --allow-permissionsapproves the package revision's declared permission grants; the approval is bound to the installed revision and its permission digest.--output json(ondiff,push,explain, anddoctor) emits a machine-readable report on stdout β fordiff/push, files with their adapter, status, and resource id plus any orphaned paths; forexplain, the translated files; fordoctor, the health checks. Human diagnostics stay on stderr.
The pivot is intentionally smaller than either native format. Shared concepts
stay portable; target-specific features live under extensions.
version: "1"
agents:
- id: build
description: Implements approved changes.
mode: primary # primary | subagent
model: sonnet # optional shared fallback
temperature: 0.2 # optional, 0.0 through 2.0
systemPrompt: |-
You are the build agent.
# promptFile: prompts/build.md # alternative to systemPrompt
skills: # emitted to both native agent formats
- test-driven-development
- verification-before-completion
permissions:
read: allow # allow | ask | deny
edit: ask
bash: # enum or command-pattern map
"go *": allow
"rm *": deny
webfetch: deny
websearch: ask
tasks:
review: allow
extensions:
claude:
model: opus # overrides the shared model for Claude
tools: [Read, Glob, Grep, Bash]
permissionMode: default
opencode:
model: anthropic/claude-sonnet-4-5
steps: 40
reasoningEffort: high
permission:
glob: allow
grep: allow
list: allow
lsp: deny
codex:
model: gpt-5.4
modelReasoningEffort: high
sandboxMode: workspace-write
approvalPolicy: on-request
webSearch: live
commands:
- id: ship
description: Ship the current changes.
template: |-
Review and ship the current changes.
agent: build
model: sonnet
# Optional global skill references retained by the pivot schema.
# Shenron does not install, copy, or emit their content.
skills:
- name: test-driven-development| Field | Rules and behavior |
|---|---|
id |
Required, unique, and matched by ^[a-z][a-z0-9-]*$. |
description |
Required; maximum 1024 characters. |
mode |
Required: primary or subagent. |
model |
Optional fallback used when the target-specific model is absent. |
temperature |
Optional number from 0.0 to 2.0. |
systemPrompt / promptFile |
Mutually exclusive. promptFile is relative to the pivot directory and must exist. |
permissions |
Portable grants translated by each adapter. |
extensions |
Target-specific overrides and fields. |
skills |
Optional ordered list of kebab-case skill names, emitted to Claude Code frontmatter and Codex instructions only (see below). Local skill existence is not required. |
These two fields have different purposes:
agents[].skillsbinds skills to an agent. It is emitted to Claude Code's agent frontmatter and added as a Codex instruction hint. OpenCode output drops the field because OpenCode v1.x forwards unknown agent keys to the LLM provider, which strict providers reject (see the table note above anddocs/SKILLS.md).- Top-level
skills: [{name: ...}]stores global references only. Shenron does not manage skill contents or install skills on another machine.
The repository's current dogfood bindings are documented in
docs/SKILLS.md.
For agents, each adapter first looks for its target-specific override:
extensions.claude.model,extensions.codex.model, orextensions.opencode.model- the shared
modelfield - the target's own default when neither is set
This lets one pivot select different providers or model aliases without forcing target-specific names into the shared field.
Claude Code derives:
read: allowβRead- any allowed bash rule β
Bash webfetch: allowβWebFetchwebsearch: allowβWebSearch- any allowed task β
Task edit: allow | ask | denyβacceptEdits | default | plan
extensions.claude.tools and extensions.claude.permissionMode override those
derived values.
OpenCode emits edit, bash, webfetch, websearch, and task directly in
its permission object. A shared read value expands to glob, grep,
list, and lsp; extensions.opencode.permission can override those four
sub-permissions individually.
Codex derives a coarse sandbox from edit, and maps websearch to Codex's
native search mode. Command-pattern bash rules, read, webfetch, and task
permissions have no equivalent per-agent Codex enforcement. Use
extensions.codex (sandboxMode, approvalPolicy, and webSearch) for an
explicit native override. Codex receives each agent skill list as an
instruction hint; Shenron does not resolve local skill paths or install skills.
Shenron ships its configuration as a directory containing a manifest and a
pivot. Hand-write one or import the first usable native config you already
have, then install it with shenron install. There is no shenron init β
every command operates on an installed package.
A package bundles a pivot together with a manifest and ships as a self-contained directory. Use packages when the same configuration should be reliably installed on many machines or released to other users.
A package directory looks like this:
my-package/
βββ shenron-package.yaml # manifest: name, version, description, skills
βββ shenron.yaml # pivot: agents, commands, permissions
shenron-package.yaml validates schemaVersion: "1", a kebab-case name
matching ^[a-z][a-z0-9-]*$, a strict semver version, a non-empty
description, and the skills.required / skills.optional arrays. Every
promptFile referenced by the pivot must stay inside the package directory.
# Local directory
./shenron install ./my-package
# Public Git repository over HTTPS (immutable tag or full commit SHA)
./shenron install https://github.com/acme/reviewers.git --ref 1.2.0
# Private/public repository over SSH (uses your ssh-agent and known_hosts)
./shenron install git@github.com:acme/reviewers.git --ref 1.2.0
./shenron install ssh://git@github.com/acme/reviewers.git --ref 1.2.0The first install copies the source into a content-addressed snapshot under
~/.shenron/packages/<name>/<digest>/. Each subsequent install from Git
requires --ref; branches and HEAD are refused. The snapshot's digest is
revalidated before every load, so a corrupted cache can never be pushed.
./shenron list # name, version, source, revision
./shenron update acme-reviewers \
--source https://github.com/acme/reviewers.git --ref 1.3.0update stages and validates the new snapshot before swapping the active
record. Old snapshots are retained.
./shenron diff acme-reviewers # preview without writing
./shenron push acme-reviewers --allow-permissionsdiff reports the same created / modified / manually-modified / orphaned
status the package flow has always reported, and additionally surfaces the
package's declared permission grants plus any required or optional skills
missing on disk.
push requires explicit approval the first time a revision declares
permission grants. The approval is bound to both the package revision and the
SHA-256 digest of the normalized grant list (state/<name>/permissions.json),
so a new revision with changed grants must be approved again. Missing required
skills abort the push; missing optional skills emit a warning and continue.
Packages also refuse to take over native resources they do not already own.
If a generated path (or a managed nested entry inside opencode.json) exists
on disk without being tracked in the package's own state file, push returns
ErrPackageCollision and aborts. push --force overwrites manually edited
package-owned files.
State for a package lives at ~/.shenron/state/<name>/.shenron-state.json,
kept outside the immutable snapshot so it survives revisions.
The sync pipeline is:
- Discover, parse, and validate the pivot.
- Generate each target's files in memory.
- Merge OpenCode agent and command fragments into the existing JSON.
- Compare generated content with disk and
.shenron-state.json. - Stage changed files, then commit the batch through a journalled transaction and record their hashes.
Each file is written atomically, and the batch is guarded by a journal
(.shenron-journal.json): the staged renames are recorded before any go live,
so a crash mid-commit is repaired on the next run by replaying the pending
renames (roll-forward), never leaving a push half-applied.
Shenron upserts pivot agents and commands into the nested agent and
command objects. Native-only entries and unrelated top-level fields are
preserved, and existing key order is retained where possible. The JSON document
is parsed and serialized again, so byte-for-byte formatting is not guaranteed.
For shenron push, the merge is upsert-only on nested entries: removing an
agent or command from the pivot does not delete its nested OpenCode entry, so
remove stale JSON entries by hand. (The package apply flow goes further β it
tracks the entries it owns in .shenron-state.json and prunes owned entries
that leave the pivot, while preserving native-only entries you added by hand.)
Standalone managed files that are no longer generated can be reported as
orphaned, but Shenron still leaves deletion to you.
.shenron-state.json, stored beside the pivot, records the hash of every file
written by a successful push. If a managed native file later differs from both
that state and the newly generated output, Shenron marks it as manually
modified and refuses to overwrite it. Review the diff, reconcile the change, or
use push --force deliberately.
- Sync is pivot-to-native; there is no automatic native-to-pivot merge after initialization.
- Native entries removed from the pivot are warned about, not deleted.
- Skill bindings are metadata only; skill directories and
SKILL.mdcontents are outside Shenron's management scope. - Skill-name validation checks kebab-case syntax, not local filesystem availability.
- OpenCode JSON is structurally preserved, not guaranteed byte-identical.
- Package installs accept local directories, public HTTPS Git repositories, or
SSH Git repositories (
git@host:pathandssh://). Remote sources require an immutable tag or full commit SHA; branches,HEAD, embedded credentials, and archive URLs are refused. SSH auth is delegated to the caller's ssh-agent and host keys are verified againstknown_hosts.
cmd/shenron/ Cobra entry point
internal/
cli/ install, list, update, diff, push, doctor, explain commands, registry, orchestration
pivot/ YAML schema, discovery, parsing, validation
package/ shenron-package.yaml manifest, immutable snapshots, Git and local install
adapter/
claude/ Markdown/frontmatter and command-file generation
codex/ TOML custom-agent and Markdown custom-prompt generation
opencode/ JSON fragments, ordered merge, prompt/command files
diff/ status calculation, unified diffs, state hashes
fsutil/ target paths and atomic file replacement
testdata/ end-to-end fixtures
docs/ PRD, plans, and skill-binding matrix
The core consumes the adapter.Adapter interface. Generate is a single,
side-effect-free pass over the whole pivot: it returns every file plus any
nested-config fragments, so one adapter instance is reusable and safe under
concurrency.
type Adapter interface {
Name() string
ValidateAgent(pivot.AgentDefinition) error
Generate(*pivot.PivotFile) (GenerationResult, error)
TargetPaths() []string
}Optional behaviors are expressed as capability interfaces (capabilities.go)
that the sync runtime probes for with type assertions:
MergingAdapter(MergeFile,ConfigPath) folds accumulated fragments into a shared config file. OpenCode implements it to merge all agent and command fragments into oneopencode.json; Claude Code and Codex generate independent files and do not.ManagedPrunerremoves leaves shenron previously managed but the current pivot no longer generates, preserving entries it never owned.PivotDirectoryAwarereceives the pivot directory to resolve relativepromptFilereferences during generation.
To add a target:
- Implement the adapter interface in
internal/adapter/<target>. - Keep target-specific translation inside that package.
- Register the adapter in
internal/cli/registry.go. - Add mapping tests, golden fixtures, and end-to-end coverage.
make test # go test ./...
make lint # golangci-lint run
make build # go build -o shenron ./cmd/shenron
make clean # remove the local binaryThe test suite contains:
- pivot parsing, validation, and discovery tests;
- adapter mapping and golden-file tests;
- ordered OpenCode merge and preservation tests;
- CLI bootstrap, diff, push, force, and orphan-scope tests;
- cobra-driven surface tests for the seven top-level commands
(
commands_test.go); - install, list, update, diff, push, permissions, skills, and foreign-collision tests against the package store;
- atomic-write and state-file tests;
- end-to-end round-trip tests for all three targets, including per-agent skills.
The root shenron.yaml, generated shenron binary, and
.shenron-state.json are local dogfood artifacts and are gitignored.
For the detailed product contract, see
docs/prd/shenron.md.
