shrinker-ai 0.3.2 → 0.4.0
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/README.md +449 -363
- package/dist/src/cli.js +144 -25
- package/dist/src/config.js +42 -0
- package/dist/src/execution/run-command.js +2 -2
- package/dist/src/filters/select-filter.js +26 -18
- package/dist/src/metrics/coverage.js +67 -0
- package/dist/src/metrics/dashboard-template.generated.js +4 -0
- package/dist/src/metrics/dashboard.js +131 -116
- package/dist/src/metrics/stats-store.js +211 -51
- package/integrations/macos/install.sh +54 -1
- package/integrations/macos/shrinker-profile.zsh +165 -98
- package/integrations/macos/uninstall.sh +50 -0
- package/integrations/windows/install.ps1 +161 -113
- package/integrations/windows/shrinker-profile.ps1 +206 -136
- package/integrations/windows/uninstall.ps1 +101 -57
- package/package.json +46 -39
- package/templates/agent-rules.md +18 -18
- package/.copilot-instructions.md +0 -28
- package/CLAUDE.md +0 -28
|
@@ -13,7 +13,9 @@ SKIP_PROFILE=0
|
|
|
13
13
|
SKIP_AGENT_RULES=0
|
|
14
14
|
COPILOT_ONLY=0
|
|
15
15
|
CLAUDE_ONLY=0
|
|
16
|
+
TRACK_UNCOVERED=""
|
|
16
17
|
PROFILE_PATH="${PROFILE_PATH:-$HOME/.zshrc}"
|
|
18
|
+
CONFIG_PATH="${SHRINKER_CONFIG_PATH:-$HOME/.shrinker/config}"
|
|
17
19
|
STEP="${SHRINKER_STEP_OFFSET:-0}"
|
|
18
20
|
|
|
19
21
|
print_message() {
|
|
@@ -34,6 +36,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
34
36
|
--skip-link) SKIP_LINK=1 ;;
|
|
35
37
|
--enable-profile-routing) ENABLE_PROFILE_ROUTING=1 ;;
|
|
36
38
|
--skip-profile) SKIP_PROFILE=1 ;;
|
|
39
|
+
--enable-uncovered-tracking) TRACK_UNCOVERED=1 ;;
|
|
40
|
+
--disable-uncovered-tracking) TRACK_UNCOVERED=0 ;;
|
|
37
41
|
--skip-agent-rules) SKIP_AGENT_RULES=1 ;;
|
|
38
42
|
--copilot-only) COPILOT_ONLY=1 ;;
|
|
39
43
|
--claude-only) CLAUDE_ONLY=1 ;;
|
|
@@ -46,6 +50,39 @@ done
|
|
|
46
50
|
if (( ENABLE_PROFILE_ROUTING == 1 && SKIP_PROFILE == 1 )); then print_message "❌" "Use either --enable-profile-routing or --skip-profile, not both." >&2; exit 1; fi
|
|
47
51
|
if (( COPILOT_ONLY == 1 && CLAUDE_ONLY == 1 )); then print_message "❌" "Use either --copilot-only or --claude-only, not both." >&2; exit 1; fi
|
|
48
52
|
|
|
53
|
+
# Prompt only on a TTY; non-interactive installs keep tracking on and leave the shell profile alone.
|
|
54
|
+
prompt_yes_no() {
|
|
55
|
+
local question="$1"
|
|
56
|
+
local default_answer="$2"
|
|
57
|
+
local reply
|
|
58
|
+
local hint="[y/N]"
|
|
59
|
+
[[ "$default_answer" == "1" ]] && hint="[Y/n]"
|
|
60
|
+
printf ' %s %s ' "$question" "$hint" > /dev/tty
|
|
61
|
+
IFS= read -r reply < /dev/tty || reply=""
|
|
62
|
+
case "$(printf '%s' "$reply" | tr '[:upper:]' '[:lower:]')" in
|
|
63
|
+
y|yes) echo 1 ;;
|
|
64
|
+
n|no) echo 0 ;;
|
|
65
|
+
*) echo "$default_answer" ;;
|
|
66
|
+
esac
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interactive=0
|
|
70
|
+
[[ -t 0 && -r /dev/tty ]] && interactive=1
|
|
71
|
+
|
|
72
|
+
if [[ -z "$TRACK_UNCOVERED" ]]; then
|
|
73
|
+
if (( interactive )); then
|
|
74
|
+
print_message "❓" "Track uncovered commands? Records which unfiltered commands cost the most tokens."
|
|
75
|
+
TRACK_UNCOVERED="$(prompt_yes_no "Enable uncovered-command tracking?" 1)"
|
|
76
|
+
else
|
|
77
|
+
TRACK_UNCOVERED=1
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if (( interactive && SKIP_PROFILE == 0 && ENABLE_PROFILE_ROUTING == 0 )); then
|
|
82
|
+
print_message "❓" "Route wrapped commands through shrinker automatically? Adds a source line to $PROFILE_PATH."
|
|
83
|
+
ENABLE_PROFILE_ROUTING="$(prompt_yes_no "Enable automatic shell routing?" 0)"
|
|
84
|
+
fi
|
|
85
|
+
|
|
49
86
|
if (( LOCAL == 0 )); then
|
|
50
87
|
package_spec="$PACKAGE_NAME"
|
|
51
88
|
[[ -z "$VERSION" ]] || package_spec="$PACKAGE_NAME@$VERSION"
|
|
@@ -60,6 +97,7 @@ if (( LOCAL == 0 )); then
|
|
|
60
97
|
$( (( SKIP_AGENT_RULES )) && echo --skip-agent-rules ) \
|
|
61
98
|
$( (( COPILOT_ONLY )) && echo --copilot-only ) \
|
|
62
99
|
$( (( CLAUDE_ONLY )) && echo --claude-only ) \
|
|
100
|
+
$( (( TRACK_UNCOVERED )) && echo --enable-uncovered-tracking || echo --disable-uncovered-tracking ) \
|
|
63
101
|
--profile-path "$PROFILE_PATH"
|
|
64
102
|
if [[ -n "${BASH_SOURCE[0]:-}" && "${BASH_SOURCE[0]}" != "$0" ]]; then
|
|
65
103
|
return 0
|
|
@@ -123,6 +161,20 @@ add_profile_integration() {
|
|
|
123
161
|
fi
|
|
124
162
|
}
|
|
125
163
|
|
|
164
|
+
set_config_value() {
|
|
165
|
+
local key="$1"
|
|
166
|
+
local value="$2"
|
|
167
|
+
mkdir -p "$(dirname "$CONFIG_PATH")"
|
|
168
|
+
chmod 700 "$(dirname "$CONFIG_PATH")" 2>/dev/null || true
|
|
169
|
+
touch "$CONFIG_PATH"
|
|
170
|
+
if grep -Eq "^[[:space:]]*$key[[:space:]]*=" "$CONFIG_PATH"; then
|
|
171
|
+
grep -Ev "^[[:space:]]*$key[[:space:]]*=" "$CONFIG_PATH" > "$CONFIG_PATH.tmp" || true
|
|
172
|
+
mv "$CONFIG_PATH.tmp" "$CONFIG_PATH"
|
|
173
|
+
fi
|
|
174
|
+
printf '%s=%s\n' "$key" "$value" >> "$CONFIG_PATH"
|
|
175
|
+
print_message "⚙️" "Set $key=$value in: $CONFIG_PATH"
|
|
176
|
+
}
|
|
177
|
+
|
|
126
178
|
print_message "📦" "Installing shrinker from: $REPO_ROOT"
|
|
127
179
|
pushd "$REPO_ROOT" >/dev/null
|
|
128
180
|
if (( SKIP_NPM_INSTALL == 0 )); then
|
|
@@ -131,7 +183,7 @@ if (( SKIP_NPM_INSTALL == 0 )); then
|
|
|
131
183
|
fi
|
|
132
184
|
if (( SKIP_BUILD == 0 )); then
|
|
133
185
|
print_message "🏗️" "Building shrinker..."
|
|
134
|
-
npm run build --silent
|
|
186
|
+
SHRINKER_BUILD_QUIET=1 npm run build --silent
|
|
135
187
|
fi
|
|
136
188
|
if (( SKIP_LINK == 0 )); then
|
|
137
189
|
print_message "🔗" "Linking shrinker globally..."
|
|
@@ -139,6 +191,7 @@ if (( SKIP_LINK == 0 )); then
|
|
|
139
191
|
fi
|
|
140
192
|
popd >/dev/null
|
|
141
193
|
|
|
194
|
+
set_config_value "SHRINKER_TRACK_UNCOVERED" "$TRACK_UNCOVERED"
|
|
142
195
|
if (( SKIP_PROFILE == 0 && ENABLE_PROFILE_ROUTING == 1 )); then add_profile_integration "$PROFILE_PATH"; fi
|
|
143
196
|
if (( SKIP_AGENT_RULES == 0 )); then
|
|
144
197
|
[[ -f "$TEMPLATE_PATH" ]] || { print_message "❌" "Agent rules template not found: $TEMPLATE_PATH" >&2; exit 1; }
|
|
@@ -1,98 +1,165 @@
|
|
|
1
|
-
typeset -gA SHRINK_RULES
|
|
2
|
-
SHRINK_RULES=(
|
|
3
|
-
git "status diff log show reflog branch tag stash"
|
|
4
|
-
npm "test t install i ci ls list"
|
|
5
|
-
docker "ps logs images compose"
|
|
6
|
-
kubectl "get describe logs"
|
|
7
|
-
gh "pr issue run"
|
|
8
|
-
rg "*"
|
|
9
|
-
find "*"
|
|
10
|
-
tail "*"
|
|
11
|
-
cat "*"
|
|
12
|
-
ls "*"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
typeset -gA SHRINK_OPTION_VALUE_FLAGS
|
|
16
|
-
SHRINK_OPTION_VALUE_FLAGS=(
|
|
17
|
-
git "-C -c --git-dir --work-tree --namespace"
|
|
18
|
-
npm "--prefix --cache --registry --workspace --userconfig -w -C"
|
|
19
|
-
docker "-H --host --context --config"
|
|
20
|
-
kubectl "-n --namespace -o --output --context --kubeconfig --cluster --user"
|
|
21
|
-
gh "-R --repo"
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
1
|
+
typeset -gA SHRINK_RULES
|
|
2
|
+
SHRINK_RULES=(
|
|
3
|
+
git "status diff log show reflog branch tag stash"
|
|
4
|
+
npm "test t install i ci ls list"
|
|
5
|
+
docker "ps logs images compose"
|
|
6
|
+
kubectl "get describe logs"
|
|
7
|
+
gh "pr issue run"
|
|
8
|
+
rg "*"
|
|
9
|
+
find "*"
|
|
10
|
+
tail "*"
|
|
11
|
+
cat "*"
|
|
12
|
+
ls "*"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
typeset -gA SHRINK_OPTION_VALUE_FLAGS
|
|
16
|
+
SHRINK_OPTION_VALUE_FLAGS=(
|
|
17
|
+
git "-C -c --git-dir --work-tree --namespace"
|
|
18
|
+
npm "--prefix --cache --registry --workspace --userconfig -w -C"
|
|
19
|
+
docker "-H --host --context --config"
|
|
20
|
+
kubectl "-n --namespace -o --output --context --kubeconfig --cluster --user"
|
|
21
|
+
gh "-R --repo"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Resolved once at load so wrapped commands never pay for reading the config file.
|
|
25
|
+
typeset -g SHRINK_TRACK_UNCOVERED_DEFAULT=""
|
|
26
|
+
_shrink_load_config() {
|
|
27
|
+
local config="${SHRINKER_CONFIG_PATH:-$HOME/.shrinker/config}"
|
|
28
|
+
[[ -r "$config" ]] || return 0
|
|
29
|
+
local value
|
|
30
|
+
value="$(sed -n 's/^[[:space:]]*SHRINKER_TRACK_UNCOVERED[[:space:]]*=[[:space:]]*\([^#[:space:]]*\).*/\1/p' "$config" | tail -n 1)"
|
|
31
|
+
SHRINK_TRACK_UNCOVERED_DEFAULT="$value"
|
|
32
|
+
}
|
|
33
|
+
_shrink_load_config
|
|
34
|
+
|
|
35
|
+
_shrink_uncovered_enabled() {
|
|
36
|
+
local value="${SHRINKER_TRACK_UNCOVERED:-$SHRINK_TRACK_UNCOVERED_DEFAULT}"
|
|
37
|
+
case "${value:l}" in
|
|
38
|
+
1|true|yes) return 0 ;;
|
|
39
|
+
*) return 1 ;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_shrink_get_subcommand() {
|
|
44
|
+
local cmd="$1"
|
|
45
|
+
shift
|
|
46
|
+
|
|
47
|
+
local -a args
|
|
48
|
+
args=("$@")
|
|
49
|
+
|
|
50
|
+
local -a value_flags
|
|
51
|
+
value_flags=(${=SHRINK_OPTION_VALUE_FLAGS[$cmd]})
|
|
52
|
+
|
|
53
|
+
local i part
|
|
54
|
+
i=1
|
|
55
|
+
while (( i <= ${#args[@]} )); do
|
|
56
|
+
part="${args[$i]}"
|
|
57
|
+
if [[ -z "$part" ]]; then
|
|
58
|
+
(( i++ ))
|
|
59
|
+
continue
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
if (( ${value_flags[(Ie)$part]} > 0 )); then
|
|
63
|
+
(( i += 2 ))
|
|
64
|
+
continue
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if [[ "$part" == -* ]]; then
|
|
68
|
+
(( i++ ))
|
|
69
|
+
continue
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
print -r -- "${part:l}"
|
|
73
|
+
return 0
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
print -r -- ""
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_shrink_should_route() {
|
|
80
|
+
local cmd="$1"
|
|
81
|
+
shift
|
|
82
|
+
|
|
83
|
+
local rules="${SHRINK_RULES[$cmd]}"
|
|
84
|
+
[[ -z "$rules" ]] && return 1
|
|
85
|
+
[[ "$rules" == *"*"* ]] && return 0
|
|
86
|
+
|
|
87
|
+
local subcommand
|
|
88
|
+
subcommand="$(_shrink_get_subcommand "$cmd" "$@")"
|
|
89
|
+
[[ -z "$subcommand" ]] && return 1
|
|
90
|
+
|
|
91
|
+
local -a allowlist
|
|
92
|
+
allowlist=(${=rules})
|
|
93
|
+
(( ${allowlist[(Ie)$subcommand]} > 0 ))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_shrink_track_uncovered() {
|
|
97
|
+
local cmd="$1"
|
|
98
|
+
local bytes="$2"
|
|
99
|
+
local status_code="$3"
|
|
100
|
+
shift 3
|
|
101
|
+
|
|
102
|
+
_shrink_uncovered_enabled || return 0
|
|
103
|
+
command -v shrinker >/dev/null 2>&1 || return 0
|
|
104
|
+
|
|
105
|
+
local subcommand
|
|
106
|
+
subcommand="$(_shrink_get_subcommand "$cmd" "$@")"
|
|
107
|
+
|
|
108
|
+
local -a track_args
|
|
109
|
+
track_args=(track --executable "$cmd" --bytes "$bytes" --exit-code "$status_code")
|
|
110
|
+
[[ -n "$subcommand" ]] && track_args+=(--subcommand "$subcommand")
|
|
111
|
+
|
|
112
|
+
(command shrinker "${track_args[@]}" >/dev/null 2>&1 &)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_shrink_run_native_tracked() {
|
|
116
|
+
local cmd="$1"
|
|
117
|
+
shift
|
|
118
|
+
|
|
119
|
+
if ! _shrink_uncovered_enabled; then
|
|
120
|
+
command "$cmd" "$@"
|
|
121
|
+
return $?
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Only measure output volume when stdout is redirected (the agent case). Interactive
|
|
125
|
+
# terminals keep the native command untouched so pagers and prompts still work.
|
|
126
|
+
if [[ -t 1 ]]; then
|
|
127
|
+
command "$cmd" "$@"
|
|
128
|
+
local status_code=$?
|
|
129
|
+
_shrink_track_uncovered "$cmd" 0 "$status_code" "$@"
|
|
130
|
+
return $status_code
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
local capture
|
|
134
|
+
capture="$(mktemp -t shrinker-track)" || { command "$cmd" "$@"; return $?; }
|
|
135
|
+
command "$cmd" "$@" | tee "$capture"
|
|
136
|
+
local status_code=${pipestatus[1]}
|
|
137
|
+
local bytes
|
|
138
|
+
bytes="$(wc -c < "$capture" | tr -d ' ')"
|
|
139
|
+
rm -f "$capture"
|
|
140
|
+
_shrink_track_uncovered "$cmd" "${bytes:-0}" "$status_code" "$@"
|
|
141
|
+
return $status_code
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_shrink_invoke_or_native() {
|
|
145
|
+
local cmd="$1"
|
|
146
|
+
shift
|
|
147
|
+
|
|
148
|
+
if _shrink_should_route "$cmd" "$@" && command -v shrinker >/dev/null 2>&1; then
|
|
149
|
+
command shrinker "$cmd" "$@"
|
|
150
|
+
return $?
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
_shrink_run_native_tracked "$cmd" "$@"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
git() { _shrink_invoke_or_native git "$@"; }
|
|
157
|
+
npm() { _shrink_invoke_or_native npm "$@"; }
|
|
158
|
+
docker() { _shrink_invoke_or_native docker "$@"; }
|
|
159
|
+
kubectl() { _shrink_invoke_or_native kubectl "$@"; }
|
|
160
|
+
gh() { _shrink_invoke_or_native gh "$@"; }
|
|
161
|
+
rg() { _shrink_invoke_or_native rg "$@"; }
|
|
162
|
+
find() { _shrink_invoke_or_native find "$@"; }
|
|
163
|
+
tail() { _shrink_invoke_or_native tail "$@"; }
|
|
164
|
+
cat() { _shrink_invoke_or_native cat "$@"; }
|
|
165
|
+
ls() { _shrink_invoke_or_native ls "$@"; }
|
|
@@ -7,7 +7,11 @@ SKIP_UNLINK=0
|
|
|
7
7
|
SKIP_AGENT_RULES=0
|
|
8
8
|
COPILOT_ONLY=0
|
|
9
9
|
CLAUDE_ONLY=0
|
|
10
|
+
PURGE_DATA=0
|
|
11
|
+
DASHBOARD_PORT=4317
|
|
10
12
|
PROFILE_PATH="${PROFILE_PATH:-$HOME/.zshrc}"
|
|
13
|
+
CONFIG_PATH="${SHRINKER_CONFIG_PATH:-$HOME/.shrinker/config}"
|
|
14
|
+
DATA_DIR="$(dirname "$CONFIG_PATH")"
|
|
11
15
|
UNINSTALL_STEP=0
|
|
12
16
|
|
|
13
17
|
print_step() {
|
|
@@ -25,6 +29,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
25
29
|
--profile-path) PROFILE_PATH="${2:-}"; shift ;;
|
|
26
30
|
--install-dir) INSTALL_DIR="${2:-}"; shift ;;
|
|
27
31
|
--remove-install-dir) REMOVE_INSTALL_DIR=1 ;;
|
|
32
|
+
--purge-data) PURGE_DATA=1 ;;
|
|
33
|
+
--port) DASHBOARD_PORT="${2:-}"; shift ;;
|
|
28
34
|
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
29
35
|
esac
|
|
30
36
|
shift
|
|
@@ -48,6 +54,38 @@ remove_profile_integration() {
|
|
|
48
54
|
mv "$profile_file.tmp" "$profile_file"
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
# The dashboard runs as a detached daemon, so unlinking the package never stops it.
|
|
58
|
+
stop_dashboard_server() {
|
|
59
|
+
local url="http://127.0.0.1:$DASHBOARD_PORT"
|
|
60
|
+
if command -v curl >/dev/null 2>&1; then
|
|
61
|
+
curl -fsS -m 3 -X POST "$url/__shrinker_shutdown" -o /dev/null 2>/dev/null || true
|
|
62
|
+
sleep 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
local pid
|
|
66
|
+
pid="$(lsof -nP -iTCP:"$DASHBOARD_PORT" -sTCP:LISTEN -t 2>/dev/null | head -n 1 || true)"
|
|
67
|
+
if [[ -n "$pid" ]]; then
|
|
68
|
+
kill "$pid" 2>/dev/null || true
|
|
69
|
+
sleep 1
|
|
70
|
+
pid="$(lsof -nP -iTCP:"$DASHBOARD_PORT" -sTCP:LISTEN -t 2>/dev/null | head -n 1 || true)"
|
|
71
|
+
[[ -n "$pid" ]] && kill -9 "$pid" 2>/dev/null || true
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Only drop installer-managed keys so hand-written settings survive.
|
|
76
|
+
remove_managed_config() {
|
|
77
|
+
[[ -f "$CONFIG_PATH" ]] || return 0
|
|
78
|
+
grep -Ev '^[[:space:]]*SHRINKER_TRACK_UNCOVERED[[:space:]]*=' "$CONFIG_PATH" > "$CONFIG_PATH.tmp" || true
|
|
79
|
+
if [[ -s "$CONFIG_PATH.tmp" ]]; then
|
|
80
|
+
mv "$CONFIG_PATH.tmp" "$CONFIG_PATH"
|
|
81
|
+
else
|
|
82
|
+
rm -f "$CONFIG_PATH.tmp" "$CONFIG_PATH"
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
print_step "🛑" "Stopping the dashboard server on port $DASHBOARD_PORT..."
|
|
87
|
+
stop_dashboard_server
|
|
88
|
+
|
|
51
89
|
if (( SKIP_UNLINK == 0 )); then
|
|
52
90
|
print_step "🔗" "Unlinking shrinker globally..."
|
|
53
91
|
npm unlink --silent --global shrinker-ai
|
|
@@ -62,6 +100,18 @@ if (( SKIP_AGENT_RULES == 0 )); then
|
|
|
62
100
|
else
|
|
63
101
|
print_step "⏭️" "Skipped managed agent rules."
|
|
64
102
|
fi
|
|
103
|
+
|
|
104
|
+
print_step "🔧" "Removing managed settings..."
|
|
105
|
+
remove_managed_config
|
|
106
|
+
rm -f "$DATA_DIR/dashboard.html"
|
|
107
|
+
|
|
108
|
+
if (( PURGE_DATA == 1 )); then
|
|
109
|
+
rm -rf "$DATA_DIR"
|
|
110
|
+
print_step "🧹" "Removed local data: $DATA_DIR"
|
|
111
|
+
elif [[ -d "$DATA_DIR" ]]; then
|
|
112
|
+
print_step "💾" "Kept saved stats in $DATA_DIR (use --purge-data to delete)."
|
|
113
|
+
fi
|
|
114
|
+
|
|
65
115
|
print_step "✅" "Uninstall complete."
|
|
66
116
|
|
|
67
117
|
if (( REMOVE_INSTALL_DIR == 1 )) && [[ -d "$INSTALL_DIR" ]]; then
|