agentsclimarketplace

Promote config

Skill scolladon/craft/skills/promote-config

Relocate a named craft config between scopes — promote local-scope to user-scope by default, or demote user-scope to local-scope with --demote. Triggers — "craft:promote-config", "promote a named config", "promote a craft config to user scope", "move a craft config to user scope", "demote a craft config to local scope".From its SKILL.md

Install
npx -y skills add scolladon/craft --skill promote-config

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.0 KB, as published. Nobody here has run it

craft:promote-config — relocate a named config between scopes

Standalone, session-owned skill. You (the session) ask the plan computer where the config should go, stage the source bytes at that destination, land through the existing lint-then-move bin, and finalize by removing the source. No worker agent is spawned.

Input: $ARGUMENTS — <name> (required), plus optional --demote and --force.

This is a MOVE by default: the source is removed once the destination copy lands clean, so the config lives at exactly one scope afterward, never both.


Preamble — plugin-root probe (read-only)

Confirm both entrypoints this skill composes exist:

test -f "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/promote-plan.js"
test -f "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/init-land.js"

If either test fails, surface a diagnostic and stop — the plugin installation is incomplete.

Parse $ARGUMENTS:

  • <name> — required. Absent ⇒ STOP: "a config name is required".
  • --demote — direction flag. Absent ⇒ promote (local→user, the default). Present ⇒ demote (user→local).
  • --force — overwrite flag, forwarded verbatim when the caller supplied it.

Procedure

Step 1 — Plan

Every decision — direction, source-existence, destination-exists refuse-vs-force, and $HOME-containment — is made by promote-plan.js, never re-derived here:

plan_out="$(node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/promote-plan.js" "$name" [--demote] [--force])"

(append --demote and/or --force only when the caller passed them).

Non-zero exit ⇒ STOP — surface the stderr diagnostic verbatim (no source config to relocate, a destination that already exists without --force, a $HOME containment escape, or an invalid name). Nothing is touched.

On exit 0, parse the three key=value lines from $plan_out:

source=<absolute source path>
dest=<absolute destination path>
scope=<user|local — the destination scope>

Bind source, dest, scope from these lines.

Step 2 — Stage

Ensure the destination directory exists, then copy the source bytes into a mktemp file created INSIDE that same directory — trailing-X template built only from the already-validated $name:

dest_dir="$(dirname "$dest")"
mkdir -p "$dest_dir"
manifest_tmp="$(mktemp "$dest_dir/.craft-${name}.tmp.XXXXXX")"
cp "$source" "$manifest_tmp"

Reuse $manifest_tmp verbatim from here on — never re-splice $name into a fresh path. mktemp's unpredictable suffix plus its O_EXCL creation is what closes the TOCTOU window between lint and move.

Step 3 — Land

Delegate the atomic lint-then-move to the existing land bin — the same one craft:init uses:

node "${CRAFT_ROOT:-${CLAUDE_PLUGIN_ROOT}}/engine/bin/init-land.js" "$manifest_tmp" "$name" --scope "$scope"

init-land.js lints $manifest_tmp at the destination root before it moves anything, so a config referencing a repo-relative path (e.g. context: docs/x.md) is REFUSED when promoted to ~/.claude — a user-scope config must be self-contained.

Non-zero exit ⇒ STOP — surface the stderr diagnostic; remove $manifest_tmp; the source is untouched (the relocation never reaches Step 4).

Step 4 — Finalize (move)

On exit 0 from Step 3, the destination copy already lints clean and is in place. Remove the source so the config lives at exactly one scope:

rm "$source"

Report the relocation with the greppable token (defined ONLY here — it does not join the skills/run/SKILL.md token family):

PROMOTE-CONFIG(<name>): local→user

or, for a --demote run:

PROMOTE-CONFIG(<name>): user→local

Error semantics

ConditionBehaviour
Missing <name>STOP: "a config name is required"
No source config at the from-scopeSTOP; promote-plan.js names the missing scope and name on stderr; nothing touched
Destination already exists, no --forceSTOP; promote-plan.js refuses with a destination-exists diagnostic naming the --force escape hatch
Destination already exists, --force passedLand proceeds; init-land.js's atomic rename overwrites the prior destination file
Source or destination user-scope path escapes $HOME containmentSTOP; promote-plan.js reports a containment failure; nothing touched
Ref-bearing / non-portable source configinit-land.js REFUSES at the destination lint before any move; $manifest_tmp is removed; source is untouched
Plugin root missing (promote-plan.js or init-land.js absent)STOP; surface which entrypoint is missing; do not invoke either bin

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.