@reddb-io/red-skills 3.18.12 → 3.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +63 -0
- package/.claude-plugin/marketplace.json +32 -0
- package/.gemini-plugin/marketplace.json +63 -0
- package/README.md +18 -18
- package/dist/code-nav.bundle.min.mjs +1 -1
- package/dist/opencode-host.bundle.min.mjs +165 -0
- package/dist/redskilled-mcp.bundle.min.mjs +372 -912
- package/dist/redskilled.bundle.min.mjs +301 -280
- package/dist/release.bundle.min.mjs +259 -0
- package/dist/rsp-core.bundle.min.mjs +199 -199
- package/dist/rsp.bundle.min.mjs +1 -1
- package/package.json +15 -3
- package/scripts/build-gemini-extension.mjs +180 -0
- package/scripts/generate-codex-manifests.mjs +291 -0
- package/scripts/generate-gemini-manifests.mjs +259 -0
- package/scripts/generate-pi-manifests.mjs +133 -0
- package/scripts/install-hermes-skills.mjs +194 -0
- package/scripts/install-opencode.sh +556 -0
- package/scripts/install-pi.sh +303 -0
- package/scripts/lib/manifest-core.mjs +106 -0
- package/scripts/validate-gemini-extension.mjs +194 -0
- package/dist/brain.bundle.min.mjs +0 -331
- package/dist/dev.bundle.min.mjs +0 -1372
- package/dist/memory.bundle.min.mjs +0 -7030
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/install-opencode.sh — install the RedSkills opencode-host
|
|
3
|
+
# adapter into a target directory or a user-scoped OpenCode-compatible config.
|
|
4
|
+
#
|
|
5
|
+
# The generated OpenCode surface includes provider/model config, MCP servers,
|
|
6
|
+
# flat skills, plugin event modules, and TUI attention config. The generator
|
|
7
|
+
# emits plugin-local trees under ./dist/opencode/<plugin>/; this script merges
|
|
8
|
+
# them into either <target>/.opencode/ or ~/.config/opencode/.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# scripts/install-opencode.sh [TARGET_DIR]
|
|
12
|
+
# scripts/install-opencode.sh --copy [TARGET_DIR]
|
|
13
|
+
# scripts/install-opencode.sh --dry-run [TARGET_DIR]
|
|
14
|
+
# scripts/install-opencode.sh --global [--host opencode|redcode]
|
|
15
|
+
# scripts/install-opencode.sh --uninstall [--global] [TARGET_DIR]
|
|
16
|
+
#
|
|
17
|
+
# Positional TARGET_DIR (default $PWD):
|
|
18
|
+
# The directory to install into. The script writes:
|
|
19
|
+
# <TARGET_DIR>/.opencode/plugin/ (plugin modules)
|
|
20
|
+
# <TARGET_DIR>/.opencode/skills/ (skill symlinks/copies)
|
|
21
|
+
# <TARGET_DIR>/opencode.json (provider + model + MCP servers)
|
|
22
|
+
# <TARGET_DIR>/tui.json (attention sounds/notifications)
|
|
23
|
+
# The user then runs `opencode <TARGET_DIR>` (or `cd <TARGET_DIR>
|
|
24
|
+
# && opencode .`) and opencode auto-loads the .opencode subtree.
|
|
25
|
+
#
|
|
26
|
+
# --global: install into ~/.config/opencode/plugins/ instead. opencode
|
|
27
|
+
# auto-loads .ts files from that directory but does NOT recurse into
|
|
28
|
+
# sub-skills/. The global mode therefore flattens the .opencode/
|
|
29
|
+
# subtree to a single plugin directory. Provider/MCP config is written to
|
|
30
|
+
# ~/.config/opencode/opencode.json(c), and attention config is written to
|
|
31
|
+
# ~/.config/opencode/tui.json(c). Existing config files are backed up first.
|
|
32
|
+
#
|
|
33
|
+
# --copy: copy SKILL.md into the target instead of symlinking. Use
|
|
34
|
+
# this when the source tree is on a different filesystem than
|
|
35
|
+
# $HOME (cross-fs symlinks are not portable).
|
|
36
|
+
#
|
|
37
|
+
# Preconditions:
|
|
38
|
+
# - This script must be run from a clone of the red-skills repo
|
|
39
|
+
# (or with a --config pointing at a .red/config.yaml that
|
|
40
|
+
# already has plugins.dev.enabled: true). The opt-in gate is
|
|
41
|
+
# fail-closed (ADR 0067); a missing enabled: true aborts the
|
|
42
|
+
# install.
|
|
43
|
+
# - pnpm install has been run at least once (the script runs it
|
|
44
|
+
# automatically on the first invocation).
|
|
45
|
+
#
|
|
46
|
+
# The script is idempotent. Re-running replaces the install in place. Uninstall
|
|
47
|
+
# removes only files that are either recorded in the RedSkills manifest or still
|
|
48
|
+
# match RedSkills-generated/source content.
|
|
49
|
+
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
|
|
52
|
+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
53
|
+
TARGET_DIR=""
|
|
54
|
+
MODE="local"
|
|
55
|
+
ACTION="install"
|
|
56
|
+
COPY="false"
|
|
57
|
+
DRY_RUN="false"
|
|
58
|
+
CONFIG_PATH="$REPO_ROOT/.red/config.yaml"
|
|
59
|
+
PLUGINS_ROOT="$REPO_ROOT/plugins"
|
|
60
|
+
OUT_DIR="$REPO_ROOT/dist/opencode"
|
|
61
|
+
PREGENERATED_TREE_MARKER="$OUT_DIR/.release-generated"
|
|
62
|
+
MANIFEST_FILE=""
|
|
63
|
+
MANIFEST_TMP=""
|
|
64
|
+
HOST="opencode"
|
|
65
|
+
HOST_LABEL="OpenCode"
|
|
66
|
+
HOST_COMMAND="opencode"
|
|
67
|
+
HOST_CONFIG_DIR="opencode"
|
|
68
|
+
|
|
69
|
+
usage() {
|
|
70
|
+
cat <<'EOF'
|
|
71
|
+
Usage: scripts/install-opencode.sh [TARGET_DIR]
|
|
72
|
+
[--global]
|
|
73
|
+
[--host opencode|redcode]
|
|
74
|
+
[--uninstall]
|
|
75
|
+
[--copy] [--dry-run]
|
|
76
|
+
|
|
77
|
+
Positional:
|
|
78
|
+
TARGET_DIR install into this directory (default: $PWD)
|
|
79
|
+
|
|
80
|
+
Options:
|
|
81
|
+
--global install into the selected host's user config
|
|
82
|
+
--host <host> OpenCode-compatible host (default: opencode)
|
|
83
|
+
--uninstall remove RedSkills files/config for the selected host
|
|
84
|
+
--copy copy SKILL.md instead of symlinking
|
|
85
|
+
--dry-run print the steps, do not write
|
|
86
|
+
|
|
87
|
+
Examples:
|
|
88
|
+
scripts/install-opencode.sh # install into $PWD
|
|
89
|
+
scripts/install-opencode.sh /path/to/my-project
|
|
90
|
+
scripts/install-opencode.sh --global # user-scoped install
|
|
91
|
+
scripts/install-opencode.sh --global --host redcode # ~/.config/redcode
|
|
92
|
+
scripts/install-opencode.sh --uninstall --global # remove user-scoped install
|
|
93
|
+
scripts/install-opencode.sh --copy /path/to/project # cross-fs safety
|
|
94
|
+
EOF
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
while [ $# -gt 0 ]; do
|
|
98
|
+
case "$1" in
|
|
99
|
+
--global) MODE="global" ;;
|
|
100
|
+
--host)
|
|
101
|
+
[ $# -ge 2 ] || { echo "error: --host requires a value" >&2; exit 2; }
|
|
102
|
+
HOST="$2"
|
|
103
|
+
shift
|
|
104
|
+
;;
|
|
105
|
+
--uninstall) ACTION="uninstall" ;;
|
|
106
|
+
--copy) COPY="true" ;;
|
|
107
|
+
--dry-run) DRY_RUN="true" ;;
|
|
108
|
+
-h|--help) usage; exit 0 ;;
|
|
109
|
+
-*) echo "error: unknown arg $1" >&2; usage; exit 2 ;;
|
|
110
|
+
*) TARGET_DIR="$1" ;;
|
|
111
|
+
esac
|
|
112
|
+
shift
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
case "$HOST" in
|
|
116
|
+
opencode)
|
|
117
|
+
HOST_LABEL="OpenCode"
|
|
118
|
+
HOST_COMMAND="opencode"
|
|
119
|
+
HOST_CONFIG_DIR="opencode"
|
|
120
|
+
;;
|
|
121
|
+
redcode)
|
|
122
|
+
HOST_LABEL="RedCode"
|
|
123
|
+
HOST_COMMAND="redcode"
|
|
124
|
+
HOST_CONFIG_DIR="redcode"
|
|
125
|
+
;;
|
|
126
|
+
*) printf 'install-opencode: unsupported host %s (expected opencode or redcode)\n' "$HOST" >&2; exit 1 ;;
|
|
127
|
+
esac
|
|
128
|
+
|
|
129
|
+
if [ "$MODE" = "local" ] && [ -z "$TARGET_DIR" ]; then
|
|
130
|
+
TARGET_DIR="$PWD"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
log() { printf 'install-%s: %s\n' "$HOST" "$*"; }
|
|
134
|
+
die() { printf 'install-%s: %s\n' "$HOST" "$*" >&2; exit 1; }
|
|
135
|
+
|
|
136
|
+
run_rm() {
|
|
137
|
+
path="$1"
|
|
138
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
139
|
+
log "(dry-run) remove $path"
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
rm -rf "$path"
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
begin_manifest() {
|
|
146
|
+
MANIFEST_FILE="$1"
|
|
147
|
+
[ "$DRY_RUN" = "false" ] || return 0
|
|
148
|
+
mkdir -p "$(dirname "$MANIFEST_FILE")"
|
|
149
|
+
MANIFEST_TMP="$MANIFEST_FILE.tmp-$$"
|
|
150
|
+
{
|
|
151
|
+
printf '# RedSkills %s install manifest\n' "$HOST_LABEL"
|
|
152
|
+
printf '# One absolute path per line. Used by scripts/install-opencode.sh --uninstall.\n'
|
|
153
|
+
} > "$MANIFEST_TMP"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
record_manifest() {
|
|
157
|
+
path="$1"
|
|
158
|
+
[ -n "$MANIFEST_TMP" ] || return 0
|
|
159
|
+
printf '%s\n' "$path" >> "$MANIFEST_TMP"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
finish_manifest() {
|
|
163
|
+
[ -n "$MANIFEST_TMP" ] || return 0
|
|
164
|
+
mv "$MANIFEST_TMP" "$MANIFEST_FILE"
|
|
165
|
+
log "wrote uninstall manifest $MANIFEST_FILE"
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
remove_manifest_paths() {
|
|
169
|
+
manifest="$1"
|
|
170
|
+
allowed_root="$2"
|
|
171
|
+
[ -f "$manifest" ] || return 1
|
|
172
|
+
|
|
173
|
+
while IFS= read -r path || [ -n "$path" ]; do
|
|
174
|
+
case "$path" in
|
|
175
|
+
""|\#*) continue ;;
|
|
176
|
+
"$allowed_root"/*)
|
|
177
|
+
if [ -e "$path" ] || [ -L "$path" ]; then
|
|
178
|
+
run_rm "$path"
|
|
179
|
+
fi
|
|
180
|
+
;;
|
|
181
|
+
*) log "(note) skipped manifest path outside $allowed_root: $path" ;;
|
|
182
|
+
esac
|
|
183
|
+
done < "$manifest"
|
|
184
|
+
|
|
185
|
+
run_rm "$manifest"
|
|
186
|
+
return 0
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
remove_generated_config() {
|
|
190
|
+
file="$1"
|
|
191
|
+
[ -f "$file" ] || return 0
|
|
192
|
+
if head -n 1 "$file" | grep -Fq 'generated by @reddb-io/red-skills'; then
|
|
193
|
+
run_rm "$file"
|
|
194
|
+
else
|
|
195
|
+
log "(note) kept $file because it is not a RedSkills-generated config"
|
|
196
|
+
fi
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
write_expected_tui() {
|
|
200
|
+
out="$1"
|
|
201
|
+
cat > "$out" <<'TUI'
|
|
202
|
+
{
|
|
203
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
204
|
+
"attention": {
|
|
205
|
+
"enabled": true,
|
|
206
|
+
"notifications": true,
|
|
207
|
+
"sound": true,
|
|
208
|
+
"volume": 0.4
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
TUI
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
remove_redskills_tui() {
|
|
215
|
+
file="$1"
|
|
216
|
+
[ -f "$file" ] || return 0
|
|
217
|
+
tmp="$(mktemp)"
|
|
218
|
+
write_expected_tui "$tmp"
|
|
219
|
+
if cmp -s "$file" "$tmp"; then
|
|
220
|
+
rm -f "$tmp"
|
|
221
|
+
run_rm "$file"
|
|
222
|
+
else
|
|
223
|
+
rm -f "$tmp"
|
|
224
|
+
log "(note) kept $file because it no longer exactly matches the RedSkills TUI template"
|
|
225
|
+
fi
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
remove_matching_source_skills() {
|
|
229
|
+
target_skills_dir="$1"
|
|
230
|
+
[ -d "$target_skills_dir" ] || return 0
|
|
231
|
+
[ -d "$PLUGINS_ROOT" ] || {
|
|
232
|
+
log "(note) no source plugins tree available; only manifest-recorded skills can be removed"
|
|
233
|
+
return 0
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
ordered_plugins=""
|
|
237
|
+
for preferred in dev memory brain; do
|
|
238
|
+
[ -d "$PLUGINS_ROOT/$preferred" ] && ordered_plugins="$ordered_plugins $preferred"
|
|
239
|
+
done
|
|
240
|
+
for plugin_dir in "$PLUGINS_ROOT"/*; do
|
|
241
|
+
[ -d "$plugin_dir" ] || continue
|
|
242
|
+
plugin="$(basename "$plugin_dir")"
|
|
243
|
+
case " $ordered_plugins " in
|
|
244
|
+
*" $plugin "*) ;;
|
|
245
|
+
*) ordered_plugins="$ordered_plugins $plugin" ;;
|
|
246
|
+
esac
|
|
247
|
+
done
|
|
248
|
+
|
|
249
|
+
for plugin in $ordered_plugins; do
|
|
250
|
+
[ -d "$PLUGINS_ROOT/$plugin/skills" ] || continue
|
|
251
|
+
while IFS= read -r source_skill; do
|
|
252
|
+
skill_name="$(basename "$(dirname "$source_skill")")"
|
|
253
|
+
target_skill="$target_skills_dir/$skill_name/SKILL.md"
|
|
254
|
+
if [ -e "$target_skill" ] && cmp -s "$target_skill" "$source_skill"; then
|
|
255
|
+
run_rm "$target_skills_dir/$skill_name"
|
|
256
|
+
fi
|
|
257
|
+
done < <(find "$PLUGINS_ROOT/$plugin/skills" -name SKILL.md -type f | sort)
|
|
258
|
+
done
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
remove_local_install() {
|
|
262
|
+
TARGET_OC="$TARGET_DIR/.opencode"
|
|
263
|
+
manifest="$TARGET_OC/redskills-install-manifest.txt"
|
|
264
|
+
remove_manifest_paths "$manifest" "$TARGET_DIR" || true
|
|
265
|
+
|
|
266
|
+
if [ -d "$TARGET_OC/plugin" ]; then
|
|
267
|
+
for file in "$TARGET_OC/plugin"/*.ts; do
|
|
268
|
+
[ -f "$file" ] || continue
|
|
269
|
+
if grep -Fq 'generated by @reddb-io/red-skills' "$file"; then
|
|
270
|
+
run_rm "$file"
|
|
271
|
+
fi
|
|
272
|
+
done
|
|
273
|
+
fi
|
|
274
|
+
|
|
275
|
+
remove_matching_source_skills "$TARGET_OC/skills"
|
|
276
|
+
remove_generated_config "$TARGET_DIR/opencode.json"
|
|
277
|
+
remove_generated_config "$TARGET_DIR/opencode.jsonc"
|
|
278
|
+
remove_redskills_tui "$TARGET_DIR/tui.json"
|
|
279
|
+
remove_redskills_tui "$TARGET_DIR/tui.jsonc"
|
|
280
|
+
log "done. removed RedSkills $HOST_LABEL files from $TARGET_DIR"
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
remove_global_install() {
|
|
284
|
+
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
285
|
+
OPENCODE_ROOT="$XDG_CONFIG_HOME/$HOST_CONFIG_DIR"
|
|
286
|
+
GLOBAL_PLUGINS_DIR="$OPENCODE_ROOT/plugins"
|
|
287
|
+
GLOBAL_SKILLS_DIR="$OPENCODE_ROOT/skills"
|
|
288
|
+
manifest="$OPENCODE_ROOT/redskills-install-manifest.txt"
|
|
289
|
+
|
|
290
|
+
remove_manifest_paths "$manifest" "$OPENCODE_ROOT" || true
|
|
291
|
+
|
|
292
|
+
for file in "$GLOBAL_PLUGINS_DIR"/redskills-*.ts; do
|
|
293
|
+
[ -e "$file" ] || continue
|
|
294
|
+
run_rm "$file"
|
|
295
|
+
done
|
|
296
|
+
|
|
297
|
+
remove_matching_source_skills "$GLOBAL_SKILLS_DIR"
|
|
298
|
+
remove_generated_config "$OPENCODE_ROOT/opencode.json"
|
|
299
|
+
remove_generated_config "$OPENCODE_ROOT/opencode.jsonc"
|
|
300
|
+
remove_redskills_tui "$OPENCODE_ROOT/tui.json"
|
|
301
|
+
remove_redskills_tui "$OPENCODE_ROOT/tui.jsonc"
|
|
302
|
+
log "done. removed RedSkills $HOST_LABEL files from $OPENCODE_ROOT"
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if [ "$ACTION" = "uninstall" ]; then
|
|
306
|
+
if [ "$MODE" = "local" ]; then
|
|
307
|
+
remove_local_install
|
|
308
|
+
else
|
|
309
|
+
remove_global_install
|
|
310
|
+
fi
|
|
311
|
+
exit 0
|
|
312
|
+
fi
|
|
313
|
+
|
|
314
|
+
# 0. Preconditions
|
|
315
|
+
[ -f "$CONFIG_PATH" ] || die "config not found at $CONFIG_PATH — run from a red-skills clone"
|
|
316
|
+
[ -d "$PLUGINS_ROOT" ] || die "plugins tree not found at $PLUGINS_ROOT"
|
|
317
|
+
|
|
318
|
+
# 1. resolve the generator (release bundle preferred, tsx fallback)
|
|
319
|
+
BUNDLE="$REPO_ROOT/dist/opencode-host.bundle.min.mjs"
|
|
320
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
321
|
+
GENERATOR="(tsx $REPO_ROOT/apps/opencode-host/src/generate.ts)"
|
|
322
|
+
elif [ -f "$BUNDLE" ]; then
|
|
323
|
+
GENERATOR="node $BUNDLE"
|
|
324
|
+
else
|
|
325
|
+
# Build-from-source fallback for checkout installs. The universal curl
|
|
326
|
+
# installer downloads the release asset above, so normal user installs do not
|
|
327
|
+
# need pnpm unless the asset is unavailable.
|
|
328
|
+
if [ ! -d "$REPO_ROOT/node_modules" ]; then
|
|
329
|
+
log "running pnpm install (first run)"
|
|
330
|
+
(cd "$REPO_ROOT" && pnpm install --frozen-lockfile 2>/dev/null || pnpm install) >/dev/null
|
|
331
|
+
fi
|
|
332
|
+
log "bundle missing — building"
|
|
333
|
+
(cd "$REPO_ROOT" && pnpm --filter @reddb-io/red-skills bundle) >/dev/null
|
|
334
|
+
GENERATOR="node $BUNDLE"
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
# 2. prepare the dist tree. Release installs prefer the pre-generated tree
|
|
338
|
+
# extracted by scripts/install.sh; source checkouts regenerate from the current
|
|
339
|
+
# tree so local development stays fresh.
|
|
340
|
+
if [ -f "$PREGENERATED_TREE_MARKER" ]; then
|
|
341
|
+
log "using release-generated dist tree under $OUT_DIR"
|
|
342
|
+
else
|
|
343
|
+
GEN_ARGS=(--config "$CONFIG_PATH" --plugins-root "$PLUGINS_ROOT" --out-dir "$OUT_DIR")
|
|
344
|
+
[ "$COPY" = "true" ] && GEN_ARGS+=(--copy)
|
|
345
|
+
|
|
346
|
+
log "generating dist tree under $OUT_DIR"
|
|
347
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
348
|
+
log "(dry-run) $GENERATOR ${GEN_ARGS[*]}"
|
|
349
|
+
else
|
|
350
|
+
(cd "$REPO_ROOT" && $GENERATOR "${GEN_ARGS[@]}")
|
|
351
|
+
fi
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
# 3. discover the plugins that have a dist subtree
|
|
355
|
+
PLUGINS=""
|
|
356
|
+
for d in "$OUT_DIR"/*/; do
|
|
357
|
+
[ -d "$d" ] || continue
|
|
358
|
+
name=$(basename "$d")
|
|
359
|
+
[ -d "$d/.opencode" ] && PLUGINS="$PLUGINS $name"
|
|
360
|
+
done
|
|
361
|
+
PLUGINS="${PLUGINS# }"
|
|
362
|
+
[ -n "$PLUGINS" ] || die "no plugin dist trees under $OUT_DIR — the generator emitted nothing"
|
|
363
|
+
|
|
364
|
+
ORDERED_PLUGINS=""
|
|
365
|
+
for preferred in dev memory brain; do
|
|
366
|
+
case " $PLUGINS " in
|
|
367
|
+
*" $preferred "*) ORDERED_PLUGINS="$ORDERED_PLUGINS $preferred" ;;
|
|
368
|
+
esac
|
|
369
|
+
done
|
|
370
|
+
for plugin in $PLUGINS; do
|
|
371
|
+
case " $ORDERED_PLUGINS " in
|
|
372
|
+
*" $plugin "*) ;;
|
|
373
|
+
*) ORDERED_PLUGINS="$ORDERED_PLUGINS $plugin" ;;
|
|
374
|
+
esac
|
|
375
|
+
done
|
|
376
|
+
PLUGINS="${ORDERED_PLUGINS# }"
|
|
377
|
+
|
|
378
|
+
# 5. install into the target
|
|
379
|
+
if [ "$MODE" = "local" ]; then
|
|
380
|
+
TARGET_OC="$TARGET_DIR/.opencode"
|
|
381
|
+
TARGET_CFG="$TARGET_DIR/opencode.json"
|
|
382
|
+
if [ "$DRY_RUN" = "false" ]; then
|
|
383
|
+
mkdir -p "$TARGET_OC/plugin" "$TARGET_OC/skills"
|
|
384
|
+
begin_manifest "$TARGET_OC/redskills-install-manifest.txt"
|
|
385
|
+
fi
|
|
386
|
+
|
|
387
|
+
for plugin in $PLUGINS; do
|
|
388
|
+
SRC="$OUT_DIR/$plugin/.opencode"
|
|
389
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
390
|
+
log "(dry-run) merge $SRC into $TARGET_OC/"
|
|
391
|
+
continue
|
|
392
|
+
fi
|
|
393
|
+
# Merge: skill symlinks/copies and plugin modules go into the
|
|
394
|
+
# shared .opencode/ subtree. Multiple RedSkills plugins share
|
|
395
|
+
# one .opencode/ root; a skill with the same name from two
|
|
396
|
+
# plugins is a build error and would have been caught by the
|
|
397
|
+
# Slice 2 name validation.
|
|
398
|
+
[ -d "$SRC/skills" ] && for skill_dir in "$SRC/skills"/*/; do
|
|
399
|
+
[ -d "$skill_dir" ] || continue
|
|
400
|
+
skill_name="$(basename "$skill_dir")"
|
|
401
|
+
dst="$TARGET_OC/skills/$skill_name"
|
|
402
|
+
if [ -e "$dst" ]; then
|
|
403
|
+
log "(note) skill $skill_name already existed in $TARGET_OC/skills/ — left in place"
|
|
404
|
+
else
|
|
405
|
+
cp -R "$skill_dir" "$dst"
|
|
406
|
+
fi
|
|
407
|
+
[ -e "$dst/SKILL.md" ] && cmp -s "$dst/SKILL.md" "$skill_dir/SKILL.md" && record_manifest "$dst"
|
|
408
|
+
done
|
|
409
|
+
[ -d "$SRC/plugin" ] && for src_ts in "$SRC/plugin"/*.ts; do
|
|
410
|
+
[ -f "$src_ts" ] || continue
|
|
411
|
+
dst="$TARGET_OC/plugin/$(basename "$src_ts")"
|
|
412
|
+
cp "$src_ts" "$dst"
|
|
413
|
+
record_manifest "$dst"
|
|
414
|
+
done
|
|
415
|
+
log "merged $plugin into $TARGET_OC/"
|
|
416
|
+
done
|
|
417
|
+
|
|
418
|
+
# Slice 1+3: write the standalone opencode.json (provider block +
|
|
419
|
+
# MCP servers from every installed plugin, deduplicated). Slice 1
|
|
420
|
+
# is now part of Slice 1+3 — the generator emits a single file
|
|
421
|
+
# with `provider> + mcp:`, and the install script writes that
|
|
422
|
+
# to the target root.
|
|
423
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
424
|
+
log "(dry-run) write $TARGET_CFG (Slice 1+3 provider block + MCP servers)"
|
|
425
|
+
else
|
|
426
|
+
STANDALONE_OUT="$TARGET_CFG.tmp-$$"
|
|
427
|
+
(cd "$REPO_ROOT" && $GENERATOR --config "$CONFIG_PATH" --plugins-root "$PLUGINS_ROOT" --out "$STANDALONE_OUT") >/dev/null
|
|
428
|
+
if [ -f "$STANDALONE_OUT" ]; then
|
|
429
|
+
mv "$STANDALONE_OUT" "$TARGET_CFG"
|
|
430
|
+
log "wrote $TARGET_CFG (provider block + MCP servers from $(echo $PLUGINS | wc -w) plugin(s))"
|
|
431
|
+
else
|
|
432
|
+
cp "$OUT_DIR/dev/opencode.json" "$TARGET_CFG"
|
|
433
|
+
log "(warning) standalone emit failed; fell back to per-plugin opencode.json"
|
|
434
|
+
fi
|
|
435
|
+
fi
|
|
436
|
+
|
|
437
|
+
# Slice 4: write tui.json to the project root with attention
|
|
438
|
+
# enabled (built-in done/error/permission/question sounds +
|
|
439
|
+
# notifications). The user can rename it to tui.jsonc and move
|
|
440
|
+
# it to ~/.config/opencode/ to make it user-scoped.
|
|
441
|
+
TARGET_TUI="$TARGET_DIR/tui.json"
|
|
442
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
443
|
+
log "(dry-run) write $TARGET_TUI (Slice 4: tui.json with attention.enabled)"
|
|
444
|
+
else
|
|
445
|
+
cat > "$TARGET_TUI" <<'TUI'
|
|
446
|
+
{
|
|
447
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
448
|
+
"attention": {
|
|
449
|
+
"enabled": true,
|
|
450
|
+
"notifications": true,
|
|
451
|
+
"sound": true,
|
|
452
|
+
"volume": 0.4
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
TUI
|
|
456
|
+
log "wrote $TARGET_TUI (Slice 4: tui.json with attention.enabled; move to ~/.config/opencode/ to make it user-scoped)"
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
finish_manifest
|
|
460
|
+
log "done. run: cd $TARGET_DIR && $HOST_COMMAND ."
|
|
461
|
+
else
|
|
462
|
+
# global mode
|
|
463
|
+
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
464
|
+
HOST_ROOT="$XDG_CONFIG_HOME/$HOST_CONFIG_DIR"
|
|
465
|
+
GLOBAL_PLUGINS_DIR="$HOST_ROOT/plugins"
|
|
466
|
+
GLOBAL_SKILLS_DIR="$HOST_ROOT/skills"
|
|
467
|
+
GLOBAL_CFG="$HOST_ROOT/opencode.json"
|
|
468
|
+
[ -f "$HOST_ROOT/opencode.jsonc" ] && GLOBAL_CFG="$HOST_ROOT/opencode.jsonc"
|
|
469
|
+
declare -A INSTALLED_SKILLS=()
|
|
470
|
+
|
|
471
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
472
|
+
log "(dry-run) flatten each plugin module into $GLOBAL_PLUGINS_DIR/redskills-<plugin>-<event>.ts"
|
|
473
|
+
log "(dry-run) flatten each skill into $GLOBAL_SKILLS_DIR/<name>/SKILL.md"
|
|
474
|
+
log "(dry-run) merge provider block into $GLOBAL_CFG"
|
|
475
|
+
else
|
|
476
|
+
mkdir -p "$GLOBAL_PLUGINS_DIR" "$GLOBAL_SKILLS_DIR"
|
|
477
|
+
begin_manifest "$HOST_ROOT/redskills-install-manifest.txt"
|
|
478
|
+
for plugin in $PLUGINS; do
|
|
479
|
+
SRC="$OUT_DIR/$plugin/.opencode"
|
|
480
|
+
[ -d "$SRC/plugin" ] && for src_ts in "$SRC/plugin"/*.ts; do
|
|
481
|
+
[ -f "$src_ts" ] || continue
|
|
482
|
+
event=$(basename "$src_ts" .ts)
|
|
483
|
+
dst="$GLOBAL_PLUGINS_DIR/redskills-$plugin-$event.ts"
|
|
484
|
+
cp "$src_ts" "$dst"
|
|
485
|
+
record_manifest "$dst"
|
|
486
|
+
log "installed $plugin/$event -> $dst"
|
|
487
|
+
done
|
|
488
|
+
[ -d "$SRC/skills" ] && for skill_dir in "$SRC/skills"/*/; do
|
|
489
|
+
[ -d "$skill_dir" ] || continue
|
|
490
|
+
skill_name=$(basename "$skill_dir")
|
|
491
|
+
if [ -n "${INSTALLED_SKILLS[$skill_name]:-}" ]; then
|
|
492
|
+
log "(note) duplicate skill $skill_name from $plugin skipped; already installed from ${INSTALLED_SKILLS[$skill_name]}"
|
|
493
|
+
continue
|
|
494
|
+
fi
|
|
495
|
+
dst="$GLOBAL_SKILLS_DIR/$skill_name"
|
|
496
|
+
rm -rf "$dst"
|
|
497
|
+
cp -R "$skill_dir" "$dst"
|
|
498
|
+
INSTALLED_SKILLS[$skill_name]="$plugin"
|
|
499
|
+
record_manifest "$dst"
|
|
500
|
+
log "installed skill $skill_name -> $dst"
|
|
501
|
+
done
|
|
502
|
+
done
|
|
503
|
+
|
|
504
|
+
# Merge provider block + MCP servers into the user's
|
|
505
|
+
# opencode.json(c). The generator emits a single standalone
|
|
506
|
+
# file with `provider> + mcp:` (Slice 1+3) for the global
|
|
507
|
+
# install; the per-plugin dist files are not used here because
|
|
508
|
+
# the global config is a single file, not a per-plugin tree.
|
|
509
|
+
if [ -f "$GLOBAL_CFG" ] && [ -s "$GLOBAL_CFG" ] && [ "$(cat "$GLOBAL_CFG" 2>/dev/null)" != "{}" ]; then
|
|
510
|
+
backup="$GLOBAL_CFG.backup-$(date +%Y%m%d%H%M%S)"
|
|
511
|
+
cp "$GLOBAL_CFG" "$backup"
|
|
512
|
+
log "backed up existing $GLOBAL_CFG -> $backup"
|
|
513
|
+
fi
|
|
514
|
+
STANDALONE_OUT="$GLOBAL_CFG.tmp-$$"
|
|
515
|
+
(cd "$REPO_ROOT" && $GENERATOR --config "$CONFIG_PATH" --plugins-root "$PLUGINS_ROOT" --out "$STANDALONE_OUT") >/dev/null
|
|
516
|
+
if [ -f "$STANDALONE_OUT" ]; then
|
|
517
|
+
mv "$STANDALONE_OUT" "$GLOBAL_CFG"
|
|
518
|
+
log "wrote $GLOBAL_CFG (provider block + MCP servers)"
|
|
519
|
+
else
|
|
520
|
+
cp "$OUT_DIR/dev/opencode.json" "$GLOBAL_CFG"
|
|
521
|
+
log "(warning) standalone emit failed; fell back to per-plugin opencode.json"
|
|
522
|
+
fi
|
|
523
|
+
|
|
524
|
+
# Slice 4: merge the tui.json with attention.enabled. opencode
|
|
525
|
+
# reads tui.json (or tui.jsonc) at the selected host config root
|
|
526
|
+
# on every session start, so this enables the built-in
|
|
527
|
+
# done / error / permission / question / subagent_done
|
|
528
|
+
# sounds + notifications globally.
|
|
529
|
+
GLOBAL_TUI="$HOST_ROOT/tui.json"
|
|
530
|
+
[ -f "$HOST_ROOT/tui.jsonc" ] && GLOBAL_TUI="$HOST_ROOT/tui.jsonc"
|
|
531
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
532
|
+
log "(dry-run) write $GLOBAL_TUI (Slice 4: tui.json with attention.enabled)"
|
|
533
|
+
else
|
|
534
|
+
if [ -f "$GLOBAL_TUI" ] && [ -s "$GLOBAL_TUI" ] && [ "$(cat "$GLOBAL_TUI" 2>/dev/null)" != "{}" ]; then
|
|
535
|
+
backup="$GLOBAL_TUI.backup-$(date +%Y%m%d%H%M%S)"
|
|
536
|
+
cp "$GLOBAL_TUI" "$backup"
|
|
537
|
+
log "backed up existing $GLOBAL_TUI -> $backup"
|
|
538
|
+
fi
|
|
539
|
+
cat > "$GLOBAL_TUI" <<'TUI'
|
|
540
|
+
{
|
|
541
|
+
"$schema": "https://opencode.ai/tui.json",
|
|
542
|
+
"attention": {
|
|
543
|
+
"enabled": true,
|
|
544
|
+
"notifications": true,
|
|
545
|
+
"sound": true,
|
|
546
|
+
"volume": 0.4
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
TUI
|
|
550
|
+
log "wrote $GLOBAL_TUI (Slice 4: tui.json with attention.enabled)"
|
|
551
|
+
fi
|
|
552
|
+
finish_manifest
|
|
553
|
+
fi
|
|
554
|
+
|
|
555
|
+
log "done. $HOST_COMMAND in any directory now picks up the same model + plugins."
|
|
556
|
+
fi
|