docks-kit 0.1.0 → 0.1.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.
Files changed (39) hide show
  1. package/AGENTS.md +14 -15
  2. package/README.md +12 -11
  3. package/SoT/.agents/skills.txt +3 -3
  4. package/SoT/.claude/settings.json +3 -26
  5. package/SoT/models.json +1 -1
  6. package/SoT/toolchain.json +3 -3
  7. package/cli/docs/install.md +39 -12
  8. package/cli/docs/overview.md +3 -4
  9. package/cli/docs/platforms.md +35 -22
  10. package/cli/src/commands/sync.ts +32 -4
  11. package/cli/src/commands/update.ts +116 -0
  12. package/cli/src/engine-native/DESIGN.md +89 -0
  13. package/cli/src/engine-native/claudeModel.ts +48 -0
  14. package/cli/src/engine-native/claudeSync.ts +771 -0
  15. package/cli/src/engine-native/codexSync.ts +439 -0
  16. package/cli/src/engine-native/codexToml.ts +67 -0
  17. package/cli/src/engine-native/exec.ts +54 -0
  18. package/cli/src/engine-native/index.ts +109 -0
  19. package/cli/src/engine-native/jq.ts +63 -0
  20. package/cli/src/engine-native/models.ts +73 -0
  21. package/cli/src/engine-native/modes.ts +133 -0
  22. package/cli/src/engine-native/output.ts +20 -0
  23. package/cli/src/engine-native/parseArgs.ts +223 -0
  24. package/cli/src/engine-native/settings.ts +41 -0
  25. package/cli/src/engine-native/skillsSync.ts +369 -0
  26. package/cli/src/engine-native/toolchain.ts +257 -0
  27. package/cli/src/engine.ts +35 -18
  28. package/cli/src/kitHome.ts +4 -4
  29. package/cli/src/main.ts +14 -1
  30. package/cli/src/manifests.ts +3 -2
  31. package/cli/tsconfig.json +1 -1
  32. package/docks-kit +6 -3
  33. package/package.json +8 -4
  34. package/lib/claude.sh +0 -846
  35. package/lib/codex.sh +0 -518
  36. package/lib/common.sh +0 -229
  37. package/lib/engine.sh +0 -153
  38. package/lib/skills.sh +0 -373
  39. package/lib/toolchain.sh +0 -222
package/lib/codex.sh DELETED
@@ -1,518 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
-
4
- # Source-order guards: this lib depends on common.sh's log/warn/err and on
5
- # the entry script's REPO_DIR. Fail fast with a clear message if sourced standalone.
6
- declare -F log >/dev/null 2>&1 || { printf '\033[1;31m[err]\033[0m %s\n' "lib/codex.sh must be sourced after lib/common.sh" >&2; exit 1; }
7
- [[ -n "${REPO_DIR:-}" ]] || { printf '\033[1;31m[err]\033[0m %s\n' "REPO_DIR must be set before sourcing lib/codex.sh" >&2; exit 1; }
8
-
9
- codex::sync() {
10
- CODEX_DIR="$HOME/.codex"
11
- CODEX_SYNCED=1
12
-
13
- local codex_settings="$REPO_DIR/SoT/.codex/config.toml"
14
- local user_codex_settings="$CODEX_DIR/config.toml"
15
- local codex_rules_dir="$REPO_DIR/SoT/.codex/rules"
16
- local user_codex_rules_dir="$CODEX_DIR/rules"
17
- local codex_agents_md="$REPO_DIR/SoT/.codex/AGENTS.md"
18
- local user_codex_agents_md="$CODEX_DIR/AGENTS.md"
19
- local codex_marketplace="$REPO_DIR/SoT/.codex/plugins/marketplace.json"
20
- local user_codex_marketplace="$AGENTS_DIR/plugins/marketplace.json"
21
-
22
- # Per-stage dry-run pattern: each helper owns its own [[ DRY_RUN ]] echo +
23
- # early return. codex::sync is the dispatcher and never inspects DRY_RUN.
24
- codex::ensure_bubblewrap
25
- [[ "$DRY_RUN" -eq 0 ]] && mkdir -p "$CODEX_DIR"
26
- codex::sync_config "$codex_settings" "$user_codex_settings"
27
- codex::sync_model
28
- codex::sync_rules "$codex_rules_dir" "$user_codex_rules_dir"
29
- codex::sync_agents_md "$codex_agents_md" "$user_codex_agents_md"
30
- codex::sync_marketplace "$codex_marketplace" "$user_codex_marketplace"
31
- codex::remove_legacy_docks_marketplace
32
- codex::sync_plugins "$codex_settings"
33
- }
34
-
35
- # Codex prefers a system bubblewrap on Linux, then falls back to its bundled
36
- # helper when user namespaces allow it. macOS uses Seatbelt natively.
37
- codex::ensure_bubblewrap() {
38
- if [[ "$DRY_RUN" -eq 1 ]]; then
39
- echo "[dry-run] verify bubblewrap installed (recommended Codex Linux sandbox runtime)"
40
- return
41
- fi
42
-
43
- codex::_bwrap_supported_os || return
44
- command -v bwrap >/dev/null 2>&1 && return
45
-
46
- if [[ "${SKIP_RTK:-0}" -eq 1 ]]; then
47
- warn "bubblewrap not installed (--skip-rtk skips auto-install). Codex may use its bundled helper if user namespaces work; recommended install: sudo apt install -y bubblewrap"
48
- return
49
- fi
50
-
51
- local pm_install
52
- pm_install=$(codex::_bwrap_detect_pm_install_cmd)
53
- if [[ -z "$pm_install" ]]; then
54
- warn "bubblewrap not installed and no supported package manager found (apt-get/dnf/pacman/zypper). Codex may use its bundled helper if user namespaces work; install system bubblewrap manually when possible."
55
- return
56
- fi
57
-
58
- warn "bubblewrap not installed - recommended for Codex Linux sandbox. Running: $pm_install (sudo prompt may appear)"
59
- if ! eval "$pm_install"; then
60
- warn "Failed to auto-install bubblewrap. Install manually: $pm_install"
61
- return
62
- fi
63
-
64
- if ! command -v bwrap >/dev/null 2>&1; then
65
- warn "Package install reported success but bwrap not on PATH — check installation manually"
66
- return
67
- fi
68
-
69
- codex::_bwrap_verify_userns
70
- }
71
-
72
- # Returns 0 if the current OS supports bubblewrap (Linux); 1 otherwise. macOS
73
- # uses Seatbelt natively so bwrap is not needed; unknown OSes get a warning.
74
- codex::_bwrap_supported_os() {
75
- case "$(uname -s)" in
76
- Darwin*) return 1 ;;
77
- Linux*) return 0 ;;
78
- *) warn "Unknown OS — skipping bubblewrap check; Codex sandbox may not work"; return 1 ;;
79
- esac
80
- }
81
-
82
- # Echoes the `sudo <pm> install -y bubblewrap` command for the first PM found
83
- # on PATH (apt-get -> dnf -> pacman -> zypper); empty string if none found.
84
- codex::_bwrap_detect_pm_install_cmd() {
85
- if command -v apt-get >/dev/null 2>&1; then
86
- echo "sudo apt-get install -y bubblewrap"
87
- elif command -v dnf >/dev/null 2>&1; then
88
- echo "sudo dnf install -y bubblewrap"
89
- elif command -v pacman >/dev/null 2>&1; then
90
- echo "sudo pacman -S --noconfirm bubblewrap"
91
- elif command -v zypper >/dev/null 2>&1; then
92
- echo "sudo zypper install -y bubblewrap"
93
- fi
94
- }
95
-
96
- # Probe whether unprivileged user namespaces work - system bubblewrap and the
97
- # bundled Codex helper both depend on them at runtime.
98
- codex::_bwrap_verify_userns() {
99
- if unshare -Ur true >/dev/null 2>&1; then
100
- log "bubblewrap installed and functional ($(bwrap --version 2>/dev/null | head -1))"
101
- else
102
- warn "bubblewrap installed but unprivileged user namespaces appear blocked. On Ubuntu 24.04+, prefer loading the AppArmor bwrap-userns-restrict profile; fallback: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0"
103
- fi
104
- }
105
-
106
- codex::sync_rules() {
107
- local codex_rules_dir="$1"
108
- local user_codex_rules_dir="$2"
109
- local rule_file user_rule_file rules_synced=0
110
-
111
- [[ -d "$codex_rules_dir" ]] || return
112
-
113
- if [[ "$DRY_RUN" -eq 1 ]]; then
114
- echo "[dry-run] cp $codex_rules_dir/*.rules -> $user_codex_rules_dir/"
115
- return
116
- fi
117
-
118
- mkdir -p "$user_codex_rules_dir"
119
- while IFS= read -r rule_file; do
120
- [[ -f "$rule_file" ]] || continue
121
- user_rule_file="$user_codex_rules_dir/$(basename "$rule_file")"
122
- [[ -f "$user_rule_file" ]] && cp "$user_rule_file" "$user_rule_file.bak"
123
- cp "$rule_file" "$user_rule_file"
124
- rules_synced=1
125
- done < <(find "$codex_rules_dir" -maxdepth 1 -type f -name '*.rules' | sort)
126
-
127
- [[ "$rules_synced" -eq 1 ]] && log "Codex rules synced"
128
- }
129
-
130
- codex::sync_agents_md() {
131
- local codex_agents_md="$1"
132
- local user_codex_agents_md="$2"
133
-
134
- [[ -f "$codex_agents_md" ]] || return
135
-
136
- if [[ "$DRY_RUN" -eq 1 ]]; then
137
- echo "[dry-run] cp $codex_agents_md -> $user_codex_agents_md"
138
- return
139
- fi
140
-
141
- [[ -f "$user_codex_agents_md" ]] && cp "$user_codex_agents_md" "$user_codex_agents_md.bak"
142
- cp "$codex_agents_md" "$user_codex_agents_md"
143
- log "Codex AGENTS.md synced"
144
- }
145
-
146
- codex::sync_config() {
147
- local codex_settings="$1"
148
- local user_codex_settings="$2"
149
-
150
- [[ -f "$codex_settings" ]] || return
151
-
152
- if [[ "$DRY_RUN" -eq 1 ]]; then
153
- echo "[dry-run] merge $codex_settings -> $user_codex_settings"
154
- return
155
- fi
156
-
157
- if [[ ! -f "$user_codex_settings" ]]; then
158
- cp "$codex_settings" "$user_codex_settings"
159
- log "Codex config installed"
160
- return
161
- fi
162
-
163
- cp "$user_codex_settings" "$user_codex_settings.bak"
164
-
165
- # Stage list is the OCP extension point: append a new stage here to grow the
166
- # merge pipeline without editing the loop. Order matters — scrub deprecated
167
- # keys before merging so SoT keys don't re-introduce a scrubbed table.
168
- local stages=(scrub_deprecated_features merge_top_level_settings merge_table_settings) stage
169
- for stage in "${stages[@]}"; do
170
- "codex::$stage" "$codex_settings" "$user_codex_settings"
171
- done
172
-
173
- # Merge is always additive (SoT keys/tables win; user-only keys and [table] blocks
174
- # preserved). --force does NOT wholesale-reset Codex config, unlike Claude settings.
175
- log "Codex config merged (backup at config.toml.bak; user-only keys/tables preserved)"
176
- }
177
-
178
- # Drop deprecated [features].use_legacy_landlock and the [features] table if
179
- # that was its only key. Codex emits a "deprecated" warning on every run while
180
- # the key is present (value=false still warns). bubblewrap is the default Linux
181
- # sandbox now; removing the override lets Codex pick it up automatically.
182
- codex::scrub_deprecated_features() {
183
- # Unified stage signature: (codex_settings, user_codex_settings). $1 is unused.
184
- local user_codex_settings="$2"
185
-
186
- [[ -f "$user_codex_settings" ]] || return 0
187
- grep -q '^use_legacy_landlock[[:space:]]*=' "$user_codex_settings" || return 0
188
-
189
- local tmp="$user_codex_settings.tmp"
190
- awk '
191
- in_features {
192
- if (/^\[/) {
193
- in_features = 0
194
- if (keep) { print header; printf "%s", body }
195
- print
196
- next
197
- }
198
- if (/^use_legacy_landlock[[:space:]]*=/) next
199
- body = body $0 "\n"
200
- if (/[^[:space:]]/) keep = 1
201
- next
202
- }
203
- /^\[features\][[:space:]]*$/ {
204
- in_features = 1
205
- header = $0
206
- body = ""
207
- keep = 0
208
- next
209
- }
210
- { print }
211
- END {
212
- if (in_features && keep) { print header; printf "%s", body }
213
- }
214
- ' "$user_codex_settings" > "$tmp" && mv "$tmp" "$user_codex_settings"
215
-
216
- log "Codex: scrubbed deprecated [features].use_legacy_landlock"
217
- }
218
-
219
- # Replace (or insert) one top-level `key = value` line in a user config.toml,
220
- # never touching [table] blocks: an existing pre-table line for the key is
221
- # replaced in place; otherwise the line is inserted just before the first table
222
- # header (or appended when no tables exist). Shared by the SoT merge loop and
223
- # the --codex-model deploy-time modifier.
224
- codex::_replace_top_level_setting() {
225
- local user_codex_settings="$1" setting_key="$2" setting_line="$3"
226
- local tmp_file="$user_codex_settings.tmp"
227
-
228
- awk -v key="$setting_key" -v replacement="$setting_line" '
229
- BEGIN { in_table = 0; replaced = 0 }
230
- /^\[/ {
231
- if (!replaced) {
232
- print replacement
233
- replaced = 1
234
- }
235
- in_table = 1
236
- print
237
- next
238
- }
239
- !in_table && $0 ~ ("^" key "[[:space:]]*=") {
240
- if (!replaced) {
241
- print replacement
242
- replaced = 1
243
- }
244
- next
245
- }
246
- { print }
247
- END {
248
- if (!replaced) {
249
- print replacement
250
- }
251
- }
252
- ' "$user_codex_settings" > "$tmp_file" && mv "$tmp_file" "$user_codex_settings"
253
- }
254
-
255
- codex::merge_top_level_settings() {
256
- local codex_settings="$1"
257
- local user_codex_settings="$2"
258
- local setting_line setting_key
259
-
260
- while IFS= read -r setting_line; do
261
- [[ -z "$setting_line" ]] && continue
262
- setting_key="${setting_line%%=*}"
263
- setting_key="${setting_key%"${setting_key##*[![:space:]]}"}"
264
-
265
- codex::_replace_top_level_setting "$user_codex_settings" "$setting_key" "$setting_line"
266
- done < <(awk '
267
- /^\[/ { exit }
268
- /^[[:space:]]*($|#)/ { next }
269
- /^[A-Za-z0-9_.-]+[[:space:]]*=/ { print }
270
- ' "$codex_settings")
271
- }
272
-
273
- # Deploy-time modifier (--codex-model=<m>): set the DEPLOYED model line. Same
274
- # contract as the Claude modifiers: only ~/.codex/config.toml changes, the SoT
275
- # keeps its pin, and a later flag-less sync restores the SoT value (the merge
276
- # re-asserts every SoT top-level key). Also callable standalone
277
- # (docks-kit model codex <m>) — hence the ${CODEX_DIR:-} fallback.
278
- codex::sync_model() {
279
- local user_codex_settings="${CODEX_DIR:-$HOME/.codex}/config.toml"
280
-
281
- [[ -n "$CODEX_MODEL" ]] || return 0
282
-
283
- if [[ "$DRY_RUN" -eq 1 ]]; then
284
- echo "[dry-run] (--codex-model) set model = \"$CODEX_MODEL\" in $user_codex_settings"
285
- return
286
- fi
287
-
288
- [[ -f "$user_codex_settings" ]] || { warn "(--codex-model) $user_codex_settings missing — skipped"; return; }
289
- codex::_replace_top_level_setting "$user_codex_settings" "model" "model = \"$CODEX_MODEL\""
290
- log "Model: deployed Codex model set to $CODEX_MODEL (SoT unchanged; flag-less sync reverts)"
291
- }
292
-
293
- codex::merge_table_settings() {
294
- local codex_settings="$1"
295
- local user_codex_settings="$2"
296
- local table_header table_block tmp_file
297
-
298
- while IFS= read -r table_header; do
299
- [[ -z "$table_header" ]] && continue
300
-
301
- table_block="$(
302
- awk -v header="$table_header" '
303
- $0 == header { printing = 1 }
304
- printing && $0 ~ /^\[/ && $0 != header { exit }
305
- printing { print }
306
- ' "$codex_settings"
307
- )"
308
-
309
- tmp_file="$user_codex_settings.tmp"
310
- awk -v header="$table_header" '
311
- $0 == header { skip = 1; next }
312
- skip && /^\[/ { skip = 0 }
313
- !skip { print }
314
- ' "$user_codex_settings" > "$tmp_file" && mv "$tmp_file" "$user_codex_settings"
315
-
316
- {
317
- printf '\n'
318
- printf '%s\n' "$table_block"
319
- } >> "$user_codex_settings"
320
- done < <(grep -E '^\[[^]]+\]' "$codex_settings")
321
- }
322
-
323
- codex::sync_marketplace() {
324
- local codex_marketplace="$1"
325
- local user_codex_marketplace="$2"
326
-
327
- [[ -f "$codex_marketplace" ]] || return
328
-
329
- if [[ "$DRY_RUN" -eq 1 ]]; then
330
- echo "[dry-run] cp $codex_marketplace -> $user_codex_marketplace"
331
- return
332
- fi
333
-
334
- mkdir -p "$AGENTS_DIR/plugins"
335
- jq . "$codex_marketplace" >/dev/null
336
-
337
- if [[ -f "$user_codex_marketplace" ]]; then
338
- if ! jq empty "$user_codex_marketplace" 2>/dev/null; then
339
- err "Skipping marketplace sync: $user_codex_marketplace is not valid JSON. Fix or delete it."
340
- return
341
- fi
342
- cp "$user_codex_marketplace" "$user_codex_marketplace.bak"
343
- if ! jq -s '
344
- .[0] as $repo | .[1] as $user |
345
- ($user * {name: ($user.name // $repo.name), interface: ($user.interface // $repo.interface)}) |
346
- .plugins = (
347
- (($user.plugins // []) + ($repo.plugins // []))
348
- | reverse
349
- | unique_by(.name)
350
- | reverse
351
- )
352
- ' "$codex_marketplace" "$user_codex_marketplace" > "$user_codex_marketplace.tmp"; then
353
- rm -f "$user_codex_marketplace.tmp"
354
- err "jq marketplace merge failed — marketplace unchanged (backup at marketplace.json.bak)"
355
- return
356
- fi
357
- mv "$user_codex_marketplace.tmp" "$user_codex_marketplace"
358
- log "Codex marketplace merged (backup at marketplace.json.bak)"
359
- else
360
- cp "$codex_marketplace" "$user_codex_marketplace"
361
- log "Codex marketplace installed"
362
- fi
363
- }
364
-
365
- codex::_marketplace_source() {
366
- local marketplace="$1"
367
- local config_file="$2"
368
-
369
- [[ -f "$config_file" ]] || return
370
-
371
- awk -v marketplace="$marketplace" '
372
- $0 == "[marketplaces." marketplace "]" {
373
- in_marketplace = 1
374
- next
375
- }
376
- /^\[/ {
377
- in_marketplace = 0
378
- }
379
- in_marketplace && /^[[:space:]]*source[[:space:]]*=/ {
380
- source = $0
381
- sub(/^[^=]+=[[:space:]]*/, "", source)
382
- sub(/[[:space:]]*#.*/, "", source)
383
- gsub(/^"|"$/, "", source)
384
- print source
385
- exit
386
- }
387
- ' "$config_file"
388
- }
389
-
390
- codex::remove_legacy_docks_marketplace() {
391
- if [[ "$DRY_RUN" -eq 1 ]]; then
392
- echo "[dry-run] remove legacy configured Codex Docks marketplace when personal marketplace is deployed"
393
- return
394
- fi
395
-
396
- if ! command -v codex >/dev/null 2>&1; then
397
- return
398
- fi
399
-
400
- case "$(codex::_marketplace_source docks "$CODEX_DIR/config.toml")" in
401
- "https://github.com/DocksDocks/docks.git"|"DocksDocks/docks")
402
- if codex plugin marketplace remove docks >/dev/null 2>&1; then
403
- log "Removed legacy configured Codex Docks marketplace; using personal marketplace file"
404
- else
405
- warn "Failed to remove legacy configured Codex Docks marketplace"
406
- fi ;;
407
- *) ;;
408
- esac
409
- }
410
-
411
- codex::_first_line() {
412
- local text="$1"
413
- text="${text%%$'\n'*}"
414
- printf '%s\n' "$text"
415
- }
416
-
417
- codex::_standalone_install_command() {
418
- printf 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"\n'
419
- }
420
-
421
- codex::_manual_plugin_refresh_command() {
422
- local codex_settings="$1"
423
- local first_plugin
424
-
425
- first_plugin="$(codex::_enabled_plugin_ids "$codex_settings" | head -n 1)"
426
- if [[ -n "$first_plugin" ]]; then
427
- printf 'codex plugin add %s\n' "$first_plugin"
428
- else
429
- printf 'codex plugin add <plugin@marketplace>\n'
430
- fi
431
- }
432
-
433
- codex::_enabled_plugin_ids() {
434
- local codex_settings="$1"
435
-
436
- [[ -f "$codex_settings" ]] || return
437
-
438
- awk '
439
- function flush_plugin() {
440
- if (plugin != "" && enabled == 1) print plugin
441
- }
442
- /^\[plugins\."[^"]+"\][[:space:]]*$/ {
443
- flush_plugin()
444
- plugin = $0
445
- sub(/^\[plugins\."/,"",plugin)
446
- sub(/"\][[:space:]]*$/,"",plugin)
447
- enabled = 0
448
- next
449
- }
450
- /^\[/ {
451
- flush_plugin()
452
- plugin = ""
453
- enabled = 0
454
- next
455
- }
456
- plugin != "" && /^[[:space:]]*enabled[[:space:]]*=[[:space:]]*true([[:space:]]*(#.*)?)?$/ {
457
- enabled = 1
458
- }
459
- END {
460
- flush_plugin()
461
- }
462
- ' "$codex_settings"
463
- }
464
-
465
- codex::sync_plugins() {
466
- local codex_settings="$1"
467
- local plugin_id refreshed_plugins=0 failed=0 add_out failure_line
468
-
469
- if [[ "$DRY_RUN" -eq 1 ]]; then
470
- echo "[dry-run] add enabled Codex plugins from SoT"
471
- return
472
- fi
473
-
474
- if ! command -v codex >/dev/null 2>&1; then
475
- warn "codex CLI not in PATH - deployed config/marketplace only; install current standalone Codex with: $(codex::_standalone_install_command); then run: $(codex::_manual_plugin_refresh_command "$codex_settings")"
476
- return
477
- fi
478
-
479
- while IFS= read -r plugin_id; do
480
- [[ -z "$plugin_id" ]] && continue
481
-
482
- if add_out=$(codex plugin add "$plugin_id" 2>&1); then
483
- refreshed_plugins=$((refreshed_plugins + 1))
484
- elif [[ "$add_out" == *"could not find a Codex CLI binary"* ]]; then
485
- warn "Codex plugin refresh hit a stale launcher/wrapper on PATH - install current standalone Codex with: $(codex::_standalone_install_command)"
486
- failed=$((failed + 1))
487
- else
488
- failure_line="$(codex::_first_line "$add_out")"
489
- warn "Codex plugin refresh failed for $plugin_id: ${failure_line:-unknown error}; run manually: codex plugin add $plugin_id"
490
- failed=$((failed + 1))
491
- fi
492
- done < <(codex::_enabled_plugin_ids "$codex_settings")
493
-
494
- if [[ "$refreshed_plugins" -gt 0 ]]; then
495
- log "Codex plugins synced (plugins: ~$refreshed_plugins)"
496
- fi
497
- if [[ "$failed" -gt 0 ]]; then
498
- warn "$failed Codex plugin operation(s) failed — re-run sync or install manually"
499
- fi
500
- }
501
-
502
- codex::summary() {
503
- local codex_config codex_plugin_count
504
-
505
- [[ "${CODEX_SYNCED:-0}" -eq 1 ]] || return
506
- echo "Codex: ${CODEX_DIR:-$HOME/.codex}"
507
- if [[ "$DRY_RUN" -eq 0 ]]; then
508
- codex_config="${CODEX_DIR:-$HOME/.codex}/config.toml"
509
- codex_plugin_count=$(codex::_enabled_plugin_ids "$codex_config" | grep -c . || true)
510
- codex_plugin_count=${codex_plugin_count:-0}
511
- echo "Codex plugins: $codex_plugin_count enabled in config.toml"
512
- fi
513
- }
514
-
515
- codex::next_steps() {
516
- [[ "${CODEX_SYNCED:-0}" -eq 1 ]] || return
517
- echo "Restart Codex to load any refreshed plugins, skills, or tools."
518
- }