Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,10 +306,11 @@ Local installs run as your current user — no root, no service user, no chown.

The default chat bridge for OpenCode. On VPS, wp-coding-agents installs post-upgrade hooks that:

- **Remove unwanted bundled skills** — Kimaki ships with skills for frameworks and tools that aren't relevant to WordPress agent workflows. The kill list (`bridges/kimaki/skills-kill-list.txt`) controls which skills are removed after each upgrade.
- **Enable only wp-coding-agents skills** — Kimaki starts with native `--enable-skill` flags for `upgrade-wp-coding-agents` and `wp-coding-agents-setup`, so bundled skills stay on disk but are hidden from the model by Kimaki's skill permissions.
- **Restore custom skills after npm upgrades** — Kimaki still loads skills from its package skills directory, so `post-upgrade.sh` re-copies wp-coding-agents' two custom skill directories from durable config after package updates.
- **Filter redundant context** — A plugin strips Kimaki's built-in memory injection and scheduling instructions from the agent context, since DM handles those concerns. Saves ~2,400 tokens per session.

To customize the kill list, edit `bridges/kimaki/skills-kill-list.txt` before running setup, or edit `/opt/kimaki-config/skills-kill-list.txt` on the server after install.
To change the skill allow-list, edit the Kimaki `--enable-skill` arguments rendered by `bridges/kimaki.sh` before running setup or upgrade.

On local installs, Kimaki installs globally via npm but without a systemd service. Run it manually:

Expand Down
69 changes: 38 additions & 31 deletions bridges/kimaki.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
# bridges/kimaki.sh — Kimaki Discord bridge.
#
# Owns install (local launchd / VPS systemd / Linux-local manual), upgrade-time
# config sync (plugins, post-upgrade.sh, skills kill-list, regression test),
# systemd + launchd template rendering, summary blocks, and the per-bridge
# assets at bridges/kimaki/ (plugins/, post-upgrade.sh, skills-kill-list.txt).
# config sync (plugins, post-upgrade.sh, regression test), systemd + launchd
# template rendering, summary blocks, and the per-bridge assets at
# bridges/kimaki/ (plugins/, post-upgrade.sh).
#
# Install layout:
# VPS: /opt/kimaki-config/{plugins,post-upgrade.sh,skills-kill-list.txt}
# VPS: /opt/kimaki-config/{plugins,post-upgrade.sh}
# + /usr/local/bin/datamachine-kimaki-session
# + /usr/local/bin/datamachine-kimaki
# + /etc/systemd/system/kimaki.service (ExecStartPre runs post-upgrade.sh)
# Local: $KIMAKI_DATA_DIR/kimaki-config/ for plugins, post-upgrade.sh +
# kill list (executed inline at upgrade time — no launchd
# ExecStartPre hook). opencode.json loads plugins directly from this
# Local: $KIMAKI_DATA_DIR/kimaki-config/ for plugins and post-upgrade.sh
# (executed inline at upgrade time — no launchd ExecStartPre hook).
# opencode.json loads plugins directly from this
# durable data-dir copy because `npm update -g kimaki` wipes package-
# local files.
# + $HOME/.local/bin/datamachine-kimaki-session
Expand All @@ -30,6 +30,25 @@ bridge_binaries() { echo "kimaki"; }
bridge_display_name() { echo "kimaki"; }
bridge_display_title() { echo "Kimaki"; }

KIMAKI_ENABLED_SKILLS=(upgrade-wp-coding-agents wp-coding-agents-setup)

_kimaki_skill_filter_args() {
local skill
for skill in "${KIMAKI_ENABLED_SKILLS[@]}"; do
printf ' --enable-skill %s' "$skill"
done
}

_kimaki_launchd_skill_filter_args() {
local skill
for skill in "${KIMAKI_ENABLED_SKILLS[@]}"; do
cat <<EOF
<string>--enable-skill</string>
<string>$skill</string>
EOF
done
}

bridge_is_ready() {
[ -n "${KIMAKI_BOT_TOKEN:-}" ]
}
Expand Down Expand Up @@ -247,7 +266,7 @@ bridge_sync_config() {
# Resolve paths per environment.
# VPS: plugins live at /opt/kimaki-config/plugins (referenced by opencode.json,
# and by ExecStartPre in kimaki.service). Config dir holds plugins +
# post-upgrade.sh + skills-kill-list.txt.
# post-upgrade.sh.
# Local: opencode.json points at $KIMAKI_DATA_DIR/kimaki-config/plugins, the
# durable source that survives `npm update -g kimaki`. Existing configs
# that still reference package-local plugin paths are migrated by the
Expand Down Expand Up @@ -277,17 +296,17 @@ bridge_sync_config() {
# setup.sh started creating it). We're in the kimaki dispatch branch, so
# kimaki IS the detected bridge and kimaki.service IS running — the
# config dir just never got bootstrapped. Create it now from the repo.
# All contents are wp-coding-agents-owned (plugins, post-upgrade.sh,
# kill list); there is no user state to preserve.
# All contents are wp-coding-agents-owned (plugins, post-upgrade.sh); there is
# no user state to preserve.
if [ "$LOCAL_MODE" = false ] && [ ! -d "$KIMAKI_CONFIG_DIR" ]; then
if [ "$DRY_RUN" = true ]; then
echo -e "${BLUE}[dry-run]${NC} Would bootstrap $KIMAKI_CONFIG_DIR from $SCRIPT_DIR/bridges/kimaki/"
else
log " $KIMAKI_CONFIG_DIR missing — bootstrapping from repo (install predates v0.4.0)"
mkdir -p "$KIMAKI_CONFIG_DIR/plugins"
UPDATED_ITEMS+=("bootstrapped $KIMAKI_CONFIG_DIR (install predates v0.4.0)")
# Fall through — the plugin/post-upgrade/kill-list copy logic below
# handles the actual file placement idempotently.
# Fall through — the plugin/post-upgrade copy logic below handles the
# actual file placement idempotently.
fi
fi

Expand Down Expand Up @@ -327,7 +346,7 @@ bridge_sync_config() {
done
fi

# Stage post-upgrade.sh and skills-kill-list.txt in KIMAKI_CONFIG_DIR.
# Stage post-upgrade.sh in KIMAKI_CONFIG_DIR.
# On VPS this is read by ExecStartPre. On local we execute it inline below.
if [ "$DRY_RUN" = false ]; then
mkdir -p "$KIMAKI_CONFIG_DIR" 2>/dev/null || true
Expand All @@ -348,33 +367,20 @@ bridge_sync_config() {
fi
fi

if [ -f "$SCRIPT_DIR/bridges/kimaki/skills-kill-list.txt" ]; then
if [ "$DRY_RUN" = true ]; then
if ! cmp -s "$SCRIPT_DIR/bridges/kimaki/skills-kill-list.txt" "$KIMAKI_CONFIG_DIR/skills-kill-list.txt" 2>/dev/null; then
echo -e "${BLUE}[dry-run]${NC} Would update $KIMAKI_CONFIG_DIR/skills-kill-list.txt"
fi
else
if ! cmp -s "$SCRIPT_DIR/bridges/kimaki/skills-kill-list.txt" "$KIMAKI_CONFIG_DIR/skills-kill-list.txt" 2>/dev/null; then
cp "$SCRIPT_DIR/bridges/kimaki/skills-kill-list.txt" "$KIMAKI_CONFIG_DIR/skills-kill-list.txt"
log " Updated $KIMAKI_CONFIG_DIR/skills-kill-list.txt"
UPDATED_ITEMS+=("kimaki-config/skills-kill-list.txt")
fi
fi
fi

# Install wp-coding-agents' Kimaki bridge helpers. They are intentionally
# outside Kimaki's npm package so `npm update -g kimaki` cannot wipe them.
_kimaki_sync_bin_helpers

# On local, execute post-upgrade.sh inline to enforce the kill list.
# On local, execute post-upgrade.sh inline to refresh package-local custom
# skills and validate plugin state.
# On VPS, kimaki.service ExecStartPre runs it on next service restart.
if [ "$LOCAL_MODE" = true ] && [ -x "$KIMAKI_CONFIG_DIR/post-upgrade.sh" ]; then
if [ "$DRY_RUN" = true ]; then
echo -e "${BLUE}[dry-run]${NC} Would run: $KIMAKI_CONFIG_DIR/post-upgrade.sh"
else
log " Running post-upgrade.sh to enforce skills kill list..."
log " Running post-upgrade.sh to refresh Kimaki config..."
if "$KIMAKI_CONFIG_DIR/post-upgrade.sh" 2>&1 | sed 's/^/ /'; then
UPDATED_ITEMS+=("ran post-upgrade.sh (enforced skills kill list)")
UPDATED_ITEMS+=("ran post-upgrade.sh (refreshed Kimaki config)")
else
warn " post-upgrade.sh exited non-zero — review output above"
fi
Expand Down Expand Up @@ -547,7 +553,7 @@ $env_block
# tolerate exit code 1 (no matches found, the happy path on a clean box).
ExecStartPre=-/usr/bin/pkill -TERM -u $SERVICE_USER -f "opencode-ai/bin/.*serve"
ExecStartPre=$KIMAKI_CONFIG_DIR/post-upgrade.sh
ExecStart=$KIMAKI_BIN --data-dir $KIMAKI_DATA_DIR --auto-restart --no-critique
ExecStart=$KIMAKI_BIN --data-dir $KIMAKI_DATA_DIR --auto-restart --no-critique$(_kimaki_skill_filter_args)
Restart=always
RestartSec=10

Expand Down Expand Up @@ -577,6 +583,7 @@ bridge_render_launchd() {
<string>$KIMAKI_DATA_DIR</string>
<string>--auto-restart</string>
<string>--no-critique</string>
$(_kimaki_launchd_skill_filter_args)
</array>
<key>WorkingDirectory</key>
<string>$SITE_PATH</string>
Expand Down
59 changes: 9 additions & 50 deletions bridges/kimaki/post-upgrade.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
#!/usr/bin/env bash
# post-upgrade.sh — Enforce kimaki skill state and validate plugin state.
# post-upgrade.sh — Restore wp-coding-agents skills and validate plugin state.
#
# Three passes run against the npm-installed kimaki package and persistent
# Two passes run against the npm-installed kimaki package and persistent
# kimaki-config directory:
# 1. KILL — remove unwanted bundled kimaki skills listed in
# skills-kill-list.txt (target: $(npm root -g)/kimaki/skills/).
# 2. RESTORE skills — re-copy wp-coding-agents skills from the persistent
# 1. RESTORE skills — re-copy wp-coding-agents skills from the persistent
# source dir (kimaki-config/skills/) into kimaki/skills/.
# 3. VERIFY plugins — confirm required wp-coding-agents opencode plugins
# 2. VERIFY plugins — confirm required wp-coding-agents opencode plugins
# exist at the persistent kimaki-config/plugins path loaded by
# opencode.json. Local installs no longer restore plugins into
# $(npm root -g)/kimaki/plugins because package-local files are
Expand Down Expand Up @@ -61,7 +59,6 @@ else
SKILLS_DIR="/usr/lib/node_modules/kimaki/skills"
fi

KILL_LIST="$(dirname "$0")/skills-kill-list.txt"
REQUIRED_PLUGINS=(dm-context-filter.ts dm-agent-sync.ts)
WP_CODING_AGENTS_SKILLS=(upgrade-wp-coding-agents wp-coding-agents-setup)

Expand All @@ -76,46 +73,12 @@ is_wp_coding_agents_skill() {
return 1
}

is_killed_skill() {
local candidate="$1"

[[ -f "$KILL_LIST" ]] || return 1

while IFS= read -r skill || [[ -n "$skill" ]]; do
[[ -z "$skill" || "$skill" == \#* ]] && continue
[[ "$candidate" == "$skill" ]] && return 0
done < "$KILL_LIST"

return 1
}

# ----------------------------------------------------------------------------
# Pass 1: KILL — remove blacklisted bundled kimaki skills.
# ----------------------------------------------------------------------------

removed=0
if [[ ! -d "$SKILLS_DIR" ]]; then
echo "kimaki-config: skills dir not found at $SKILLS_DIR, skipping kill pass"
elif [[ ! -f "$KILL_LIST" ]]; then
echo "kimaki-config: kill list not found at $KILL_LIST, skipping kill pass"
else
while IFS= read -r skill || [[ -n "$skill" ]]; do
# Skip comments and blank lines
[[ -z "$skill" || "$skill" == \#* ]] && continue
target="$SKILLS_DIR/$skill"
if [[ -d "$target" ]]; then
rm -rf "$target"
echo "kimaki-config: removed skill $skill"
removed=$((removed + 1))
fi
done < "$KILL_LIST"
fi

# ----------------------------------------------------------------------------
# Pass 2: RESTORE skills — re-copy wp-coding-agents skills from the
# Pass 1: RESTORE skills — re-copy wp-coding-agents skills from the
# persistent source dir into the npm-managed skills dir. Idempotent: `rm -rf`
# before each `cp -r` so a stale copy always gets replaced by the current
# source.
# source. Kimaki 0.13 native --enable-skill flags hide non-allowlisted bundled
# skills at runtime, so this script no longer deletes package-owned skill dirs.
# ----------------------------------------------------------------------------

if [[ -n "${KIMAKI_SKILL_SOURCE_DIR:-}" ]]; then
Expand All @@ -135,10 +98,6 @@ elif [[ -d "$SKILL_SOURCE_DIR" ]]; then
for skill_dir in "$SKILL_SOURCE_DIR"/*/; do
[[ -d "$skill_dir" ]] || continue
skill_name="$(basename "$skill_dir")"
if is_killed_skill "$skill_name"; then
echo "kimaki-config: skipped killed skill $skill_name"
continue
fi
if ! is_wp_coding_agents_skill "$skill_name"; then
echo "kimaki-config: skipped unmanaged skill $skill_name"
continue
Expand All @@ -156,7 +115,7 @@ else
fi

# ----------------------------------------------------------------------------
# Pass 3: VERIFY plugins — opencode.json now loads wp-coding-agents plugins
# Pass 2: VERIFY plugins — opencode.json now loads wp-coding-agents plugins
# directly from persistent kimaki-config/plugins. When the target and source are
# the same directory (the default), there is nothing to restore. An explicit
# KIMAKI_PLUGINS_DIR override still receives a best-effort copy for operator
Expand Down Expand Up @@ -219,4 +178,4 @@ else
done
fi

echo "kimaki-config: done ($removed skills removed, $skills_restored skills restored, $plugins_restored plugins restored, $missing_required_plugins required plugins missing)"
echo "kimaki-config: done ($skills_restored skills restored, $plugins_restored plugins restored, $missing_required_plugins required plugins missing)"
21 changes: 0 additions & 21 deletions bridges/kimaki/skills-kill-list.txt

This file was deleted.

6 changes: 6 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Changed
- Kimaki services now use native `--enable-skill` filtering for the wp-coding-agents skills instead of deleting bundled skill directories after npm upgrades (#139).
- `bridges/kimaki/post-upgrade.sh` still restores wp-coding-agents custom skills into Kimaki's package skills path, but no longer prunes arbitrary bundled skills.

## [1.1.0] - 2026-05-17

### Added
Expand Down
4 changes: 4 additions & 0 deletions tests/__snapshots__/bridges/kimaki-launchd
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<string>/home/chubes/.kimaki</string>
<string>--auto-restart</string>
<string>--no-critique</string>
<string>--enable-skill</string>
<string>upgrade-wp-coding-agents</string>
<string>--enable-skill</string>
<string>wp-coding-agents-setup</string>
</array>
<key>WorkingDirectory</key>
<string>/var/www/site</string>
Expand Down
2 changes: 1 addition & 1 deletion tests/__snapshots__/bridges/kimaki-systemd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Environment=DATAMACHINE_SITE_PATH=/var/www/site
# tolerate exit code 1 (no matches found, the happy path on a clean box).
ExecStartPre=-/usr/bin/pkill -TERM -u chubes -f "opencode-ai/bin/.*serve"
ExecStartPre=/opt/kimaki-config/post-upgrade.sh
ExecStart=/usr/bin/kimaki --data-dir /home/chubes/.kimaki --auto-restart --no-critique
ExecStart=/usr/bin/kimaki --data-dir /home/chubes/.kimaki --auto-restart --no-critique --enable-skill upgrade-wp-coding-agents --enable-skill wp-coding-agents-setup
Restart=always
RestartSec=10

Expand Down
38 changes: 19 additions & 19 deletions tests/post-upgrade-restore.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
# tests/post-upgrade-restore.sh — smoke test for bridges/kimaki/post-upgrade.sh
#
# Verifies the three passes (kill, restore skills, verify plugins) using
# temp-dir env overrides so the test never touches the real npm install or
# user config.
# Verifies the restore-skill and verify-plugin passes using temp-dir env
# overrides so the test never touches the real npm install or user config.
#
# What we cover:
# 1. Kill pass removes a blacklisted skill from the simulated skills dir.
# 2. Skill restore pass copies wp-coding-agents SKILL.md trees from the
# 1. Skill restore pass copies wp-coding-agents SKILL.md trees from the
# persistent source back into the (wiped) skills dir.
# 2. Native Kimaki --enable-skill filtering replaces the old directory
# deletion workaround, so bundled skills in the package dir are left alone.
# 3. Default plugin pass is a no-op because opencode loads the persistent
# source dir directly.
# 4. Explicit KIMAKI_PLUGINS_DIR compatibility override still copies *.ts
Expand Down Expand Up @@ -49,7 +49,9 @@ mkdir -p "$LIVE_SKILLS" "$SRC_SKILLS" "$SRC_PLUGINS"
# Note: deliberately NOT creating LIVE_PLUGINS — explicit override mode must
# still mkdir it.

# Seed a blacklisted skill that the kill pass should remove.
# Seed package-owned skills that post-upgrade must leave alone. Native Kimaki
# --enable-skill flags now hide bundled skills at runtime instead of deleting
# package directories.
mkdir -p "$LIVE_SKILLS/blacklisted-skill"
echo "stub" > "$LIVE_SKILLS/blacklisted-skill/SKILL.md"
mkdir -p "$LIVE_SKILLS/persisted-blacklisted-skill"
Expand Down Expand Up @@ -94,17 +96,12 @@ cat > "$SRC_PLUGINS/dm-agent-sync.ts" <<'EOF'
export default async () => ({})
EOF

# Build a temp skills-kill-list.txt next to a copied post-upgrade.sh so the
# script's `dirname "$0"` lookup finds it.
# Copy post-upgrade.sh into a temp directory so the test executes the same way
# systemd/local upgrade hooks do without touching the real install.
TEST_SCRIPT_DIR="$TMP/kimaki-config-dir"
mkdir -p "$TEST_SCRIPT_DIR"
cp "$POST_UPGRADE" "$TEST_SCRIPT_DIR/post-upgrade.sh"
chmod +x "$TEST_SCRIPT_DIR/post-upgrade.sh"
cat > "$TEST_SCRIPT_DIR/skills-kill-list.txt" <<'EOF'
# test kill list
blacklisted-skill
persisted-blacklisted-skill
EOF

# Run the script with explicit env overrides so it never touches the real
# npm install or user config.
Expand Down Expand Up @@ -147,12 +144,15 @@ assert_log_contains_file() {
fi
}

# Pass 1: kill pass removed the blacklisted skill.
assert_missing "$LIVE_SKILLS/blacklisted-skill"
assert_log_contains "removed skill blacklisted-skill"
assert_missing "$LIVE_SKILLS/persisted-blacklisted-skill"
assert_log_contains "removed skill persisted-blacklisted-skill"
assert_log_contains "skipped killed skill persisted-blacklisted-skill"
# Native skill filtering replaced directory pruning, so package-owned bundled
# skills remain on disk.
assert_present "$LIVE_SKILLS/blacklisted-skill/SKILL.md"
assert_present "$LIVE_SKILLS/persisted-blacklisted-skill/SKILL.md"
if grep -q "removed skill" "$TMP/run1.log"; then
echo "FAIL: post-upgrade should not remove skill directories"
cat "$TMP/run1.log"
exit 1
fi

# Pass 2: skill restore copied the allowed SKILL.md tree and skipped unmanaged skills.
assert_present "$LIVE_SKILLS/upgrade-wp-coding-agents/SKILL.md"
Expand Down
Loading
Loading