1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
#!/bin/bash
# pi's settings.json, generated. Dry run by default; pass `yes` to apply.
set -euo pipefail
# shellcheck source-path=SCRIPTDIR
# shellcheck source=lib.sh
source "$(dirname "$0")/lib.sh" "$@"
# ~/.pi/agent/settings.json is a real file built from config/pi/settings.json
# plus every ~/.config/pi/settings.d/*.json that another layer links in. It
# cannot be a symlink into a repo: pi has no second global settings file to
# layer with, and it writes to this one at runtime (lastChangelogVersion,
# /model, `pi install`), which used to dirty the tracked file.
#
# Merging the sources: list keys (packages, extensions, skills, prompts,
# themes) are concatenated in file order, a package deduplicated by its source
# (an object entry beats a bare string); any other key takes the last file's
# value. Relative paths in a fragment's `extensions`, `skills`, `prompts` and
# `themes` resolve against the fragment's real location, so a layer can point
# at files in its own checkout.
#
# Merging with what is already there is three-way, against what this script
# generated last time (kept in ~/.local/state/dotfiles/pi-settings.json): a key
# -- or a list entry, a package by its source -- that pi or the operator has not
# changed since then takes the new generated value, removal included; one they
# changed keeps their value. With no record of a last run, what is there is kept
# and what is missing is added. So a /model choice or a `pi install` survives
# relinks, and a package dropped from a repo really goes. Keys nothing generates
# (lastChangelogVersion) are simply kept.
#
# Written under pi's own settings lock, via a temporary file and a rename; a run
# that finds the lock held skips and says so, and the next link catches up.
#
# Run after other layers' link.sh, so their fragments are in place.
pi_dir="$HOME/.pi/agent"
if [[ ! -d "$pi_dir" ]]
then
exit 0
fi
pi_status="$(DRY_RUN="$is_dry_run" python3 "$dotfiles_dir/link/pi-settings.py" \
"$config_dir/pi/settings.json" \
"$xdg_config_dir/pi/settings.d" \
"$pi_dir/settings.json" \
"${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles/pi-settings.json" \
"$config_dir")"
case "${pi_status%%:*}" in
wrote) green "pi settings: ${pi_status#*:}" ;;
unchanged) green "pi settings: unchanged" ;;
would-write) yellow "pi settings will be generated: ${pi_status#*:}" ;;
locked) yellow "pi settings: pi holds its settings lock; left as is, the next link catches up" ;;
error) red "pi settings: ${pi_status#*:}" ;;
esac
# pi's models.json, generated too. It used to be a plain symlink to the tracked
# file, which left a machine whose models come from somewhere other than a
# vendor's own API with nowhere to say so: pi reads providers from exactly one
# path (join(getAgentDir(), "models.json")), and a `providers` block in a
# settings fragment is inert, since the settings schema has no such key.
#
# So the same shape as the settings above: the repo's file, then every
# ~/.config/pi/models.d/*.json another layer or this machine puts there. Unlike
# the settings this is a plain generated file with no three-way merge -- pi
# never writes it, keeping its runtime state in the sibling models-store.json --
# so what lands is whatever the repo and the fragments say. A hand-edit of
# ~/.pi/agent/models.json does not survive a relink; put it in a fragment.
pi_models_out="$(DRY_RUN="$is_dry_run" python3 "$dotfiles_dir/link/pi-models.py" \
"$config_dir/pi/models.json" \
"$xdg_config_dir/pi/models.d" \
"$pi_dir/models.json")"
while IFS= read -r line
do
case "${line%%:*}" in
note) yellow "pi models: ${line#*:}" ;;
wrote) green "pi models: ${line#*:}" ;;
unchanged) green "pi models: unchanged" ;;
would-write) yellow "pi models will be generated: ${line#*:}" ;;
error) red "pi models: ${line#*:}" ;;
esac
done <<< "$pi_models_out"
|