shrinker-ai 0.1.0 → 0.3.1
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/.copilot-instructions.md +26 -0
- package/CLAUDE.md +26 -0
- package/README.md +98 -30
- package/integrations/macos/install.sh +149 -0
- package/integrations/macos/shrinker-profile.zsh +98 -0
- package/integrations/macos/uninstall.sh +70 -0
- package/integrations/windows/install.ps1 +113 -0
- package/integrations/windows/uninstall.ps1 +57 -0
- package/package.json +15 -7
- package/integrations/install-shrinker.ps1 +0 -166
- package/integrations/install.ps1 +0 -46
- package/integrations/uninstall-shrinker.ps1 +0 -79
- package/integrations/uninstall.ps1 +0 -35
- /package/integrations/{shrinker-profile.ps1 → windows/shrinker-profile.ps1} +0 -0
package/.copilot-instructions.md
CHANGED
|
@@ -1,2 +1,28 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
<!-- shrinker agent rules start -->
|
|
5
|
+
Use `shrinker` for high-volume terminal outputs to reduce tokens.
|
|
6
|
+
|
|
7
|
+
Routing policy:
|
|
8
|
+
- `git status|diff|log|show|reflog|branch|tag|stash` -> `shrinker git ...`
|
|
9
|
+
- `npm test|t|install|i|ci|ls|list` -> `shrinker npm ...`
|
|
10
|
+
- `docker ps|logs|images|compose` -> `shrinker docker ...`
|
|
11
|
+
- `kubectl get|describe|logs` -> `shrinker kubectl ...`
|
|
12
|
+
- `gh pr|issue|run` -> `shrinker gh ...`
|
|
13
|
+
- `rg`, `find`, `tail`, `cat`, `ls`, `dir` -> `shrinker <command> ...`
|
|
14
|
+
|
|
15
|
+
Bypass shrinker when:
|
|
16
|
+
- command modifies remote state and output volume is already tiny
|
|
17
|
+
- command requires interactive stdin
|
|
18
|
+
- command explicitly needs `shrinker --raw`
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
- `shrinker git log -n 20`
|
|
22
|
+
- `shrinker rg "pattern" src`
|
|
23
|
+
- `shrinker docker logs api --tail 500`
|
|
24
|
+
<!-- shrinker agent rules end -->
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
package/CLAUDE.md
CHANGED
|
@@ -1,2 +1,28 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
<!-- shrinker agent rules start -->
|
|
5
|
+
Use `shrinker` for high-volume terminal outputs to reduce tokens.
|
|
6
|
+
|
|
7
|
+
Routing policy:
|
|
8
|
+
- `git status|diff|log|show|reflog|branch|tag|stash` -> `shrinker git ...`
|
|
9
|
+
- `npm test|t|install|i|ci|ls|list` -> `shrinker npm ...`
|
|
10
|
+
- `docker ps|logs|images|compose` -> `shrinker docker ...`
|
|
11
|
+
- `kubectl get|describe|logs` -> `shrinker kubectl ...`
|
|
12
|
+
- `gh pr|issue|run` -> `shrinker gh ...`
|
|
13
|
+
- `rg`, `find`, `tail`, `cat`, `ls`, `dir` -> `shrinker <command> ...`
|
|
14
|
+
|
|
15
|
+
Bypass shrinker when:
|
|
16
|
+
- command modifies remote state and output volume is already tiny
|
|
17
|
+
- command requires interactive stdin
|
|
18
|
+
- command explicitly needs `shrinker --raw`
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
- `shrinker git log -n 20`
|
|
22
|
+
- `shrinker rg "pattern" src`
|
|
23
|
+
- `shrinker docker logs api --tail 500`
|
|
24
|
+
<!-- shrinker agent rules end -->
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
package/README.md
CHANGED
|
@@ -18,53 +18,89 @@ this POC proves that a few conservative, deterministic filters can save useful c
|
|
|
18
18
|
|
|
19
19
|
Requires Node.js 22.13 or newer. This is the first Node 22 release where the built-in SQLite module no longer requires an experimental flag.
|
|
20
20
|
|
|
21
|
-
### One-command install (
|
|
21
|
+
### One-command install (macOS zsh)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl -fsSL https://raw.githubusercontent.com/ivanduplenskikh/shrinker/main/integrations/macos/install.sh | bash
|
|
25
|
+
```
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
### One-command install (Windows PowerShell)
|
|
24
28
|
|
|
25
29
|
```powershell
|
|
26
|
-
irm https://raw.githubusercontent.com/ivanduplenskikh/shrinker/main/integrations/install.ps1 | iex
|
|
30
|
+
irm https://raw.githubusercontent.com/ivanduplenskikh/shrinker/main/integrations/windows/install.ps1 | iex
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
###
|
|
33
|
+
### Install from npm package
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
Windows PowerShell:
|
|
32
36
|
|
|
33
37
|
```powershell
|
|
34
|
-
|
|
38
|
+
npm install --global shrinker-ai --registry=https://registry.npmjs.org
|
|
39
|
+
$pkg = Join-Path ((npm root --global).Trim()) "shrinker-ai"
|
|
40
|
+
pwsh -ExecutionPolicy Bypass -File (Join-Path $pkg "integrations\\windows\\install.ps1") -Local -SkipNpmInstall -SkipBuild -SkipLink
|
|
35
41
|
```
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
To enable automatic PowerShell routing, add `-EnableProfileRouting` to the final command.
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
macOS zsh:
|
|
40
46
|
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
```bash
|
|
48
|
+
npm install --global shrinker-ai --registry=https://registry.npmjs.org
|
|
49
|
+
pkg="$(npm root --global)/shrinker-ai"
|
|
50
|
+
bash "$pkg/integrations/macos/install.sh" --local --skip-npm-install --skip-build --skip-link --enable-profile-routing
|
|
45
51
|
```
|
|
46
52
|
|
|
47
|
-
|
|
53
|
+
### Install from local checkout
|
|
54
|
+
|
|
55
|
+
Windows PowerShell:
|
|
48
56
|
|
|
49
57
|
```powershell
|
|
50
|
-
pwsh -ExecutionPolicy Bypass -File .\integrations\install
|
|
58
|
+
pwsh -ExecutionPolicy Bypass -File .\integrations\windows\install.ps1 -Local
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
macOS zsh:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
bash ./integrations/macos/install.sh --local
|
|
51
65
|
```
|
|
52
66
|
|
|
53
|
-
This writes managed guidance blocks to:
|
|
67
|
+
This writes managed guidance blocks globally to:
|
|
54
68
|
|
|
55
|
-
-
|
|
56
|
-
-
|
|
69
|
+
- `~/.copilot/copilot-instructions.md`
|
|
70
|
+
- `~/.claude/CLAUDE.md`
|
|
57
71
|
|
|
58
|
-
The shared guidance source is `templates/agent-rules.md`; the
|
|
72
|
+
The shared guidance source is `templates/agent-rules.md`; the files above are created in the corresponding global agent directory.
|
|
59
73
|
|
|
60
74
|
The rules tell agents to prefer `shrinker <command>` for high-volume commands while leaving native commands untouched.
|
|
61
75
|
|
|
62
|
-
###
|
|
76
|
+
### Uninstall
|
|
77
|
+
|
|
78
|
+
If you installed from npm, remove the package first:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm uninstall --global shrinker-ai --registry=https://registry.npmjs.org
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
If you also installed profile/rules through the package scripts, run the matching local uninstaller from the installed package before uninstalling:
|
|
63
85
|
|
|
64
|
-
|
|
86
|
+
Windows PowerShell:
|
|
65
87
|
|
|
66
88
|
```powershell
|
|
67
|
-
|
|
89
|
+
$pkg = Join-Path ((npm root --global).Trim()) "shrinker-ai"
|
|
90
|
+
pwsh -ExecutionPolicy Bypass -File (Join-Path $pkg "integrations\\windows\\uninstall.ps1") -SkipUnlink
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
macOS zsh:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pkg="$(npm root --global)/shrinker-ai"
|
|
97
|
+
bash "$pkg/integrations/macos/uninstall.sh" --skip-unlink
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
macOS one-liner uninstall:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
curl -fsSL https://raw.githubusercontent.com/ivanduplenskikh/shrinker/main/integrations/macos/uninstall.sh | bash
|
|
68
104
|
```
|
|
69
105
|
|
|
70
106
|
### Local repo install/uninstall (contributors)
|
|
@@ -72,26 +108,48 @@ irm https://raw.githubusercontent.com/ivanduplenskikh/shrinker/main/integrations
|
|
|
72
108
|
If you cloned this repository and want to run scripts directly from the local path:
|
|
73
109
|
|
|
74
110
|
```powershell
|
|
75
|
-
pwsh -ExecutionPolicy Bypass -File .\integrations\install
|
|
111
|
+
pwsh -ExecutionPolicy Bypass -File .\integrations\windows\install.ps1 -Local
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or on macOS:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bash ./integrations/macos/install.sh --local
|
|
76
118
|
```
|
|
77
119
|
|
|
78
120
|
To uninstall:
|
|
79
121
|
|
|
80
122
|
```powershell
|
|
81
|
-
pwsh -ExecutionPolicy Bypass -File .\integrations\
|
|
123
|
+
pwsh -ExecutionPolicy Bypass -File .\integrations\windows\install.ps1 -Uninstall
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or on macOS:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
bash ./integrations/macos/install.sh --uninstall
|
|
82
130
|
```
|
|
83
131
|
|
|
84
132
|
Uninstall options:
|
|
85
133
|
|
|
86
134
|
```powershell
|
|
87
|
-
#
|
|
88
|
-
pwsh -ExecutionPolicy Bypass -File .\integrations\
|
|
135
|
+
# Keep shrinker command installed, but remove profile integration and managed rules
|
|
136
|
+
pwsh -ExecutionPolicy Bypass -File .\integrations\windows\install.ps1 -Uninstall -SkipUnlink
|
|
137
|
+
|
|
138
|
+
# Keep managed rules files unchanged while uninstalling command/profile hooks
|
|
139
|
+
pwsh -ExecutionPolicy Bypass -File .\integrations\windows\install.ps1 -Uninstall -SkipAgentRules
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
macOS uninstall options:
|
|
89
143
|
|
|
90
|
-
|
|
91
|
-
|
|
144
|
+
```bash
|
|
145
|
+
# Keep shrinker command installed, but remove profile integration and managed rules
|
|
146
|
+
bash ./integrations/macos/install.sh --uninstall --skip-unlink
|
|
147
|
+
|
|
148
|
+
# Keep managed rules files unchanged while uninstalling command/profile hooks
|
|
149
|
+
bash ./integrations/macos/install.sh --uninstall --skip-agent-rules
|
|
92
150
|
```
|
|
93
151
|
|
|
94
|
-
If your current terminal had already loaded `shrinker-profile.ps1`, restart terminal (or remove loaded wrapper functions) to fully return to native command behavior.
|
|
152
|
+
If your current terminal had already loaded `shrinker-profile.ps1` or `shrinker-profile.zsh`, restart terminal (or remove loaded wrapper functions) to fully return to native command behavior.
|
|
95
153
|
|
|
96
154
|
```powershell
|
|
97
155
|
npm install
|
|
@@ -144,11 +202,21 @@ if (!(Test-Path $PROFILE)) {
|
|
|
144
202
|
New-Item -ItemType File -Path $PROFILE -Force | Out-Null
|
|
145
203
|
}
|
|
146
204
|
|
|
147
|
-
$integration = (Resolve-Path .\integrations\shrinker-profile.ps1).Path
|
|
205
|
+
$integration = (Resolve-Path .\integrations\windows\shrinker-profile.ps1).Path
|
|
148
206
|
Add-Content $PROFILE "`n. `"$integration`""
|
|
149
207
|
. $PROFILE
|
|
150
208
|
```
|
|
151
209
|
|
|
210
|
+
macOS zsh profile integration:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
echo '' >> ~/.zshrc
|
|
214
|
+
echo '# >>> shrinker integration >>>' >> ~/.zshrc
|
|
215
|
+
echo 'source "'"$(pwd)/integrations/macos/shrinker-profile.zsh"'"' >> ~/.zshrc
|
|
216
|
+
echo '# <<< shrinker integration <<<' >> ~/.zshrc
|
|
217
|
+
source ~/.zshrc
|
|
218
|
+
```
|
|
219
|
+
|
|
152
220
|
The default rules are:
|
|
153
221
|
|
|
154
222
|
```text
|
|
@@ -164,7 +232,7 @@ rg/find/tail/cat/ls/dir -> shrinker <command>
|
|
|
164
232
|
git push, git fetch, and all other commands -> native executable
|
|
165
233
|
```
|
|
166
234
|
|
|
167
|
-
Edit `$global:ShrinkPowerShellRules` in `integrations\shrinker-profile.ps1` to change the allowlist. The router is now option-aware for common global flags, so forms like `git -C <path> log` and `kubectl --context prod get pods` are routed correctly.
|
|
235
|
+
Edit `$global:ShrinkPowerShellRules` in `integrations\windows\shrinker-profile.ps1` to change the allowlist. The router is now option-aware for common global flags, so forms like `git -C <path> log` and `kubectl --context prod get pods` are routed correctly.
|
|
168
236
|
|
|
169
237
|
## Savings statistics
|
|
170
238
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PACKAGE_NAME="shrinker-ai"
|
|
5
|
+
REGISTRY="https://registry.npmjs.org"
|
|
6
|
+
VERSION=""
|
|
7
|
+
LOCAL=0
|
|
8
|
+
SKIP_NPM_INSTALL=0
|
|
9
|
+
SKIP_BUILD=0
|
|
10
|
+
SKIP_LINK=0
|
|
11
|
+
ENABLE_PROFILE_ROUTING=0
|
|
12
|
+
SKIP_PROFILE=0
|
|
13
|
+
SKIP_AGENT_RULES=0
|
|
14
|
+
COPILOT_ONLY=0
|
|
15
|
+
CLAUDE_ONLY=0
|
|
16
|
+
PROFILE_PATH="${PROFILE_PATH:-$HOME/.zshrc}"
|
|
17
|
+
STEP=0
|
|
18
|
+
|
|
19
|
+
print_message() {
|
|
20
|
+
local emoji="$1"
|
|
21
|
+
local message="$2"
|
|
22
|
+
STEP=$((STEP + 1))
|
|
23
|
+
printf '%d. %s %s\n' "$STEP" "$emoji" "$message"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
while [[ $# -gt 0 ]]; do
|
|
27
|
+
case "$1" in
|
|
28
|
+
--local) LOCAL=1 ;;
|
|
29
|
+
--package-name) PACKAGE_NAME="${2:-}"; shift ;;
|
|
30
|
+
--registry) REGISTRY="${2:-}"; shift ;;
|
|
31
|
+
--version) VERSION="${2:-}"; shift ;;
|
|
32
|
+
--skip-npm-install) SKIP_NPM_INSTALL=1 ;;
|
|
33
|
+
--skip-build) SKIP_BUILD=1 ;;
|
|
34
|
+
--skip-link) SKIP_LINK=1 ;;
|
|
35
|
+
--enable-profile-routing) ENABLE_PROFILE_ROUTING=1 ;;
|
|
36
|
+
--skip-profile) SKIP_PROFILE=1 ;;
|
|
37
|
+
--skip-agent-rules) SKIP_AGENT_RULES=1 ;;
|
|
38
|
+
--copilot-only) COPILOT_ONLY=1 ;;
|
|
39
|
+
--claude-only) CLAUDE_ONLY=1 ;;
|
|
40
|
+
--profile-path) PROFILE_PATH="${2:-}"; shift ;;
|
|
41
|
+
*) print_message "❌" "Unknown option: $1" >&2; exit 1 ;;
|
|
42
|
+
esac
|
|
43
|
+
shift
|
|
44
|
+
done
|
|
45
|
+
|
|
46
|
+
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
|
+
if (( COPILOT_ONLY == 1 && CLAUDE_ONLY == 1 )); then print_message "❌" "Use either --copilot-only or --claude-only, not both." >&2; exit 1; fi
|
|
48
|
+
|
|
49
|
+
if (( LOCAL == 0 )); then
|
|
50
|
+
package_spec="$PACKAGE_NAME"
|
|
51
|
+
[[ -z "$VERSION" ]] || package_spec="$PACKAGE_NAME@$VERSION"
|
|
52
|
+
print_message "📦" "Installing $package_spec from $REGISTRY..."
|
|
53
|
+
npm install --silent --global "$package_spec" "--registry=$REGISTRY"
|
|
54
|
+
package_root="$(npm root --global)/$PACKAGE_NAME"
|
|
55
|
+
entrypoint="$package_root/integrations/macos/install.sh"
|
|
56
|
+
[[ -f "$entrypoint" ]] || { print_message "❌" "Installed package installer not found: $entrypoint" >&2; exit 1; }
|
|
57
|
+
bash "$entrypoint" --local --skip-npm-install --skip-build --skip-link \
|
|
58
|
+
$( (( ENABLE_PROFILE_ROUTING )) && echo --enable-profile-routing ) \
|
|
59
|
+
$( (( SKIP_PROFILE )) && echo --skip-profile ) \
|
|
60
|
+
$( (( SKIP_AGENT_RULES )) && echo --skip-agent-rules ) \
|
|
61
|
+
$( (( COPILOT_ONLY )) && echo --copilot-only ) \
|
|
62
|
+
$( (( CLAUDE_ONLY )) && echo --claude-only ) \
|
|
63
|
+
--profile-path "$PROFILE_PATH"
|
|
64
|
+
exit 0
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
68
|
+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
69
|
+
TEMPLATE_PATH="$REPO_ROOT/templates/agent-rules.md"
|
|
70
|
+
INTEGRATION_PATH="$SCRIPT_DIR/shrinker-profile.zsh"
|
|
71
|
+
BLOCK_START="<!-- shrinker agent rules start -->"
|
|
72
|
+
BLOCK_END="<!-- shrinker agent rules end -->"
|
|
73
|
+
|
|
74
|
+
node_version_raw="$(node -v 2>/dev/null || true)"
|
|
75
|
+
[[ -n "$node_version_raw" ]] || { print_message "❌" "Node.js was not found on PATH. Install Node.js 22.13+ first." >&2; exit 1; }
|
|
76
|
+
node_version="${node_version_raw#v}"
|
|
77
|
+
IFS='.' read -r node_major node_minor _ <<< "$node_version"
|
|
78
|
+
(( node_major > 22 || (node_major == 22 && node_minor >= 13) )) || { print_message "❌" "Node.js 22.13+ is required. Found $node_version_raw" >&2; exit 1; }
|
|
79
|
+
|
|
80
|
+
set_agent_rules() {
|
|
81
|
+
local target="$1"
|
|
82
|
+
local body="$2"
|
|
83
|
+
local block="$BLOCK_START"$'\n'"$body"$'\n'"$BLOCK_END"
|
|
84
|
+
mkdir -p "$(dirname "$target")"
|
|
85
|
+
if [[ -f "$target" ]] && grep -Fq "$BLOCK_START" "$target" && grep -Fq "$BLOCK_END" "$target"; then
|
|
86
|
+
local block_file
|
|
87
|
+
block_file="$(mktemp "$(dirname "$target")/.shrinker-rules.XXXXXX")"
|
|
88
|
+
printf '%s\n' "$block" > "$block_file"
|
|
89
|
+
awk -v start="$BLOCK_START" -v end="$BLOCK_END" -v block_file="$block_file" '
|
|
90
|
+
function print_block(line) {
|
|
91
|
+
while ((getline line < block_file) > 0) print line
|
|
92
|
+
close(block_file)
|
|
93
|
+
}
|
|
94
|
+
{
|
|
95
|
+
if (index($0, start) > 0) {
|
|
96
|
+
if (!replaced) print_block()
|
|
97
|
+
replaced = 1
|
|
98
|
+
inblock = 1
|
|
99
|
+
next
|
|
100
|
+
}
|
|
101
|
+
if (inblock && index($0, end) > 0) {
|
|
102
|
+
inblock = 0
|
|
103
|
+
next
|
|
104
|
+
}
|
|
105
|
+
if (!inblock) print
|
|
106
|
+
}
|
|
107
|
+
' "$target" > "$target.tmp"
|
|
108
|
+
rm -f "$block_file"
|
|
109
|
+
mv "$target.tmp" "$target"
|
|
110
|
+
elif [[ -f "$target" ]]; then printf '\n%s\n' "$block" >> "$target"
|
|
111
|
+
else printf '%s\n' "$block" > "$target"; fi
|
|
112
|
+
print_message "📄" "Installed managed rules in: $target"
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
add_profile_integration() {
|
|
116
|
+
local profile_file="$1"
|
|
117
|
+
mkdir -p "$(dirname "$profile_file")"; touch "$profile_file"
|
|
118
|
+
if ! grep -Fq '# >>> shrinker integration >>>' "$profile_file"; then
|
|
119
|
+
printf '\n# >>> shrinker integration >>>\nsource "%s"\n# <<< shrinker integration <<<\n\n' "$INTEGRATION_PATH" >> "$profile_file"
|
|
120
|
+
fi
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
print_message "📦" "Installing shrinker from: $REPO_ROOT"
|
|
124
|
+
pushd "$REPO_ROOT" >/dev/null
|
|
125
|
+
if (( SKIP_NPM_INSTALL == 0 )); then
|
|
126
|
+
print_message "📥" "Installing dependencies..."
|
|
127
|
+
npm install --silent
|
|
128
|
+
fi
|
|
129
|
+
if (( SKIP_BUILD == 0 )); then
|
|
130
|
+
print_message "🏗️" "Building shrinker..."
|
|
131
|
+
npm run build --silent
|
|
132
|
+
fi
|
|
133
|
+
if (( SKIP_LINK == 0 )); then
|
|
134
|
+
print_message "🔗" "Linking shrinker globally..."
|
|
135
|
+
npm link --silent
|
|
136
|
+
fi
|
|
137
|
+
popd >/dev/null
|
|
138
|
+
|
|
139
|
+
if (( SKIP_PROFILE == 0 && ENABLE_PROFILE_ROUTING == 1 )); then add_profile_integration "$PROFILE_PATH"; fi
|
|
140
|
+
if (( SKIP_AGENT_RULES == 0 )); then
|
|
141
|
+
[[ -f "$TEMPLATE_PATH" ]] || { print_message "❌" "Agent rules template not found: $TEMPLATE_PATH" >&2; exit 1; }
|
|
142
|
+
rules_body="$(cat "$TEMPLATE_PATH")"
|
|
143
|
+
(( CLAUDE_ONLY )) || set_agent_rules "$HOME/.copilot/copilot-instructions.md" "$rules_body"
|
|
144
|
+
(( COPILOT_ONLY )) || set_agent_rules "$HOME/.claude/CLAUDE.md" "$rules_body"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
print_message "✅" "Install complete."
|
|
148
|
+
echo ""
|
|
149
|
+
echo "💡 Try: shrinker help"
|
|
@@ -0,0 +1,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
|
+
_shrink_get_subcommand() {
|
|
25
|
+
local cmd="$1"
|
|
26
|
+
shift
|
|
27
|
+
|
|
28
|
+
local -a args
|
|
29
|
+
args=("$@")
|
|
30
|
+
|
|
31
|
+
local -a value_flags
|
|
32
|
+
value_flags=(${=SHRINK_OPTION_VALUE_FLAGS[$cmd]})
|
|
33
|
+
|
|
34
|
+
local i part
|
|
35
|
+
i=1
|
|
36
|
+
while (( i <= ${#args[@]} )); do
|
|
37
|
+
part="${args[$i]}"
|
|
38
|
+
if [[ -z "$part" ]]; then
|
|
39
|
+
(( i++ ))
|
|
40
|
+
continue
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
if (( ${value_flags[(Ie)$part]} > 0 )); then
|
|
44
|
+
(( i += 2 ))
|
|
45
|
+
continue
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [[ "$part" == -* ]]; then
|
|
49
|
+
(( i++ ))
|
|
50
|
+
continue
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
print -r -- "${part:l}"
|
|
54
|
+
return 0
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
print -r -- ""
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
_shrink_should_route() {
|
|
61
|
+
local cmd="$1"
|
|
62
|
+
shift
|
|
63
|
+
|
|
64
|
+
local rules="${SHRINK_RULES[$cmd]}"
|
|
65
|
+
[[ -z "$rules" ]] && return 1
|
|
66
|
+
[[ "$rules" == *"*"* ]] && return 0
|
|
67
|
+
|
|
68
|
+
local subcommand
|
|
69
|
+
subcommand="$(_shrink_get_subcommand "$cmd" "$@")"
|
|
70
|
+
[[ -z "$subcommand" ]] && return 1
|
|
71
|
+
|
|
72
|
+
local -a allowlist
|
|
73
|
+
allowlist=(${=rules})
|
|
74
|
+
(( ${allowlist[(Ie)$subcommand]} > 0 ))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_shrink_invoke_or_native() {
|
|
78
|
+
local cmd="$1"
|
|
79
|
+
shift
|
|
80
|
+
|
|
81
|
+
if _shrink_should_route "$cmd" "$@" && command -v shrinker >/dev/null 2>&1; then
|
|
82
|
+
command shrinker "$cmd" "$@"
|
|
83
|
+
return $?
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
command "$cmd" "$@"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
git() { _shrink_invoke_or_native git "$@"; }
|
|
90
|
+
npm() { _shrink_invoke_or_native npm "$@"; }
|
|
91
|
+
docker() { _shrink_invoke_or_native docker "$@"; }
|
|
92
|
+
kubectl() { _shrink_invoke_or_native kubectl "$@"; }
|
|
93
|
+
gh() { _shrink_invoke_or_native gh "$@"; }
|
|
94
|
+
rg() { _shrink_invoke_or_native rg "$@"; }
|
|
95
|
+
find() { _shrink_invoke_or_native find "$@"; }
|
|
96
|
+
tail() { _shrink_invoke_or_native tail "$@"; }
|
|
97
|
+
cat() { _shrink_invoke_or_native cat "$@"; }
|
|
98
|
+
ls() { _shrink_invoke_or_native ls "$@"; }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
INSTALL_DIR="${HOME}/.shrinker-src"
|
|
5
|
+
REMOVE_INSTALL_DIR=0
|
|
6
|
+
SKIP_UNLINK=0
|
|
7
|
+
SKIP_AGENT_RULES=0
|
|
8
|
+
COPILOT_ONLY=0
|
|
9
|
+
CLAUDE_ONLY=0
|
|
10
|
+
PROFILE_PATH="${PROFILE_PATH:-$HOME/.zshrc}"
|
|
11
|
+
UNINSTALL_STEP=0
|
|
12
|
+
|
|
13
|
+
print_step() {
|
|
14
|
+
local emoji="$1" message="$2"
|
|
15
|
+
UNINSTALL_STEP=$((UNINSTALL_STEP + 1))
|
|
16
|
+
printf '%d. %s %s\n' "$UNINSTALL_STEP" "$emoji" "$message"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
while [[ $# -gt 0 ]]; do
|
|
20
|
+
case "$1" in
|
|
21
|
+
--skip-unlink) SKIP_UNLINK=1 ;;
|
|
22
|
+
--skip-agent-rules) SKIP_AGENT_RULES=1 ;;
|
|
23
|
+
--copilot-only) COPILOT_ONLY=1 ;;
|
|
24
|
+
--claude-only) CLAUDE_ONLY=1 ;;
|
|
25
|
+
--profile-path) PROFILE_PATH="${2:-}"; shift ;;
|
|
26
|
+
--install-dir) INSTALL_DIR="${2:-}"; shift ;;
|
|
27
|
+
--remove-install-dir) REMOVE_INSTALL_DIR=1 ;;
|
|
28
|
+
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
29
|
+
esac
|
|
30
|
+
shift
|
|
31
|
+
done
|
|
32
|
+
|
|
33
|
+
if (( COPILOT_ONLY == 1 && CLAUDE_ONLY == 1 )); then echo "Use either --copilot-only or --claude-only, not both." >&2; exit 1; fi
|
|
34
|
+
BLOCK_START="<!-- shrinker agent rules start -->"
|
|
35
|
+
BLOCK_END="<!-- shrinker agent rules end -->"
|
|
36
|
+
|
|
37
|
+
remove_agent_rules() {
|
|
38
|
+
local target="$1"
|
|
39
|
+
[[ -f "$target" ]] || return 0
|
|
40
|
+
awk -v start="$BLOCK_START" -v end="$BLOCK_END" 'BEGIN{inblock=0} {if(index($0,start)>0){inblock=1;next} if(inblock&&index($0,end)>0){inblock=0;next} if(!inblock)print}' "$target" > "$target.tmp"
|
|
41
|
+
mv "$target.tmp" "$target"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
remove_profile_integration() {
|
|
45
|
+
local profile_file="$1"
|
|
46
|
+
[[ -f "$profile_file" ]] || return 0
|
|
47
|
+
awk -v start='# >>> shrinker integration >>>' -v end='# <<< shrinker integration <<<' 'BEGIN{inblock=0} {if(index($0,start)>0){inblock=1;next} if(inblock&&index($0,end)>0){inblock=0;next} if(!inblock)print}' "$profile_file" > "$profile_file.tmp"
|
|
48
|
+
mv "$profile_file.tmp" "$profile_file"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (( SKIP_UNLINK == 0 )); then
|
|
52
|
+
print_step "🔗" "Unlinking shrinker globally..."
|
|
53
|
+
npm unlink --silent --global shrinker-ai
|
|
54
|
+
else
|
|
55
|
+
print_step "⏭️" "Skipped global npm unlink."
|
|
56
|
+
fi
|
|
57
|
+
print_step "🔧" "Removing shell profile integration..."
|
|
58
|
+
remove_profile_integration "$PROFILE_PATH"
|
|
59
|
+
if (( SKIP_AGENT_RULES == 0 )); then
|
|
60
|
+
(( CLAUDE_ONLY )) || remove_agent_rules "$HOME/.copilot/copilot-instructions.md"
|
|
61
|
+
(( COPILOT_ONLY )) || remove_agent_rules "$HOME/.claude/CLAUDE.md"
|
|
62
|
+
else
|
|
63
|
+
print_step "⏭️" "Skipped managed agent rules."
|
|
64
|
+
fi
|
|
65
|
+
print_step "✅" "Uninstall complete."
|
|
66
|
+
|
|
67
|
+
if (( REMOVE_INSTALL_DIR == 1 )) && [[ -d "$INSTALL_DIR" ]]; then
|
|
68
|
+
rm -rf "$INSTALL_DIR"
|
|
69
|
+
print_step "🧹" "Removed install directory: $INSTALL_DIR"
|
|
70
|
+
fi
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$Local,
|
|
3
|
+
[string]$PackageName = "shrinker-ai",
|
|
4
|
+
[string]$Registry = "https://registry.npmjs.org",
|
|
5
|
+
[string]$Version,
|
|
6
|
+
[switch]$SkipNpmInstall,
|
|
7
|
+
[switch]$SkipBuild,
|
|
8
|
+
[switch]$SkipLink,
|
|
9
|
+
[switch]$EnableProfileRouting,
|
|
10
|
+
[switch]$SkipProfile,
|
|
11
|
+
[switch]$SkipAgentRules,
|
|
12
|
+
[switch]$CopilotOnly,
|
|
13
|
+
[switch]$ClaudeOnly,
|
|
14
|
+
[string]$ProfilePath = $PROFILE
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
$ErrorActionPreference = "Stop"
|
|
18
|
+
$InstallStep = 0
|
|
19
|
+
|
|
20
|
+
function Write-InstallStep {
|
|
21
|
+
param([string]$Emoji, [string]$Message)
|
|
22
|
+
$script:InstallStep++
|
|
23
|
+
Write-Host "$($script:InstallStep). $Emoji $Message"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if ($EnableProfileRouting -and $SkipProfile) { throw "Use either -EnableProfileRouting or -SkipProfile, not both." }
|
|
27
|
+
if ($CopilotOnly -and $ClaudeOnly) { throw "Use either -CopilotOnly or -ClaudeOnly, not both." }
|
|
28
|
+
|
|
29
|
+
function Set-AgentRules {
|
|
30
|
+
param([string]$Body)
|
|
31
|
+
$start = "<!-- shrinker agent rules start -->"
|
|
32
|
+
$end = "<!-- shrinker agent rules end -->"
|
|
33
|
+
foreach ($target in @((Join-Path $HOME ".copilot\copilot-instructions.md"), (Join-Path $HOME ".claude\CLAUDE.md"))) {
|
|
34
|
+
if ($ClaudeOnly -and $target.EndsWith(".copilot-instructions.md")) { continue }
|
|
35
|
+
if ($CopilotOnly -and $target.EndsWith("CLAUDE.md")) { continue }
|
|
36
|
+
New-Item -ItemType Directory -Path (Split-Path $target) -Force | Out-Null
|
|
37
|
+
$content = if (Test-Path $target) { Get-Content $target -Raw } else { "" }
|
|
38
|
+
$block = "$start`n$Body`n$end"
|
|
39
|
+
if ($null -ne $content -and $content.Contains($start) -and $content.Contains($end)) {
|
|
40
|
+
$content = [regex]::Replace($content, [regex]::Escape($start) + ".*?" + [regex]::Escape($end), $block, 'Singleline')
|
|
41
|
+
Set-Content $target $content
|
|
42
|
+
} elseif ($content) { Add-Content $target "`n$block`n" }
|
|
43
|
+
else { Set-Content $target "$block`n" }
|
|
44
|
+
Write-InstallStep "📄" "Installed managed rules in: $target"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Add-ProfileIntegration {
|
|
49
|
+
param([string]$ProfileFile, [string]$IntegrationFile)
|
|
50
|
+
if (-not (Test-Path $ProfileFile)) { New-Item -ItemType File -Path $ProfileFile -Force | Out-Null }
|
|
51
|
+
$start = "# >>> shrinker integration >>>"
|
|
52
|
+
if ((Get-Content $ProfileFile -Raw).Contains($start)) { return }
|
|
53
|
+
Add-Content $ProfileFile "`n$start`n. `"$IntegrationFile`"`n# <<< shrinker integration <<<`n"
|
|
54
|
+
Write-InstallStep "🔧" "Added shrinker integration block to profile: $ProfileFile"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (-not $Local) {
|
|
58
|
+
$packageSpec = if ($Version) { "$PackageName@$Version" } else { $PackageName }
|
|
59
|
+
Write-InstallStep "📦" "Installing $packageSpec from $Registry..."
|
|
60
|
+
& npm install --silent --global $packageSpec "--registry=$Registry"
|
|
61
|
+
if ($LASTEXITCODE -ne 0) { throw "npm package installation failed." }
|
|
62
|
+
Write-InstallStep "🔎" "Locating the installed package..."
|
|
63
|
+
$globalRoot = (& npm root --global).Trim()
|
|
64
|
+
if (-not $globalRoot) { throw "Could not determine the global npm package directory." }
|
|
65
|
+
$packageRoot = Join-Path $globalRoot ($PackageName -replace '/', '\')
|
|
66
|
+
$entrypoint = Join-Path $packageRoot "integrations\windows\install.ps1"
|
|
67
|
+
if (-not (Test-Path $entrypoint)) { throw "Installed package installer not found: $entrypoint" }
|
|
68
|
+
Write-InstallStep "⚙️" "Configuring the installed package..."
|
|
69
|
+
& pwsh -ExecutionPolicy Bypass -File $entrypoint -Local -SkipNpmInstall -SkipBuild -SkipLink -EnableProfileRouting:$EnableProfileRouting -SkipProfile:$SkipProfile -SkipAgentRules:$SkipAgentRules -CopilotOnly:$CopilotOnly -ClaudeOnly:$ClaudeOnly -ProfilePath $ProfilePath
|
|
70
|
+
if ($LASTEXITCODE -ne 0) { throw "Repository installer failed with exit code $LASTEXITCODE" }
|
|
71
|
+
Write-InstallStep "✅" "Install complete."
|
|
72
|
+
exit 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
$scriptDir = $PSScriptRoot
|
|
76
|
+
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
|
|
77
|
+
$templatePath = Join-Path $repoRoot "templates\agent-rules.md"
|
|
78
|
+
$integrationPath = (Resolve-Path (Join-Path $scriptDir "shrinker-profile.ps1")).Path
|
|
79
|
+
if (-not $SkipAgentRules) {
|
|
80
|
+
if (-not (Test-Path $templatePath)) { throw "Agent rules template not found: $templatePath" }
|
|
81
|
+
$rulesBody = Get-Content $templatePath -Raw
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$versionText = & node -v 2>$null
|
|
85
|
+
if (-not $versionText) { throw "Node.js was not found on PATH. Install Node.js 22.13+ first." }
|
|
86
|
+
$version = [version]($versionText.TrimStart('v'))
|
|
87
|
+
if ($version -lt [version]'22.13.0') { throw "Node.js 22.13+ is required. Found $versionText" }
|
|
88
|
+
|
|
89
|
+
Push-Location $repoRoot
|
|
90
|
+
try {
|
|
91
|
+
Write-InstallStep "📦" "Installing shrinker from: $repoRoot"
|
|
92
|
+
if (-not $SkipNpmInstall) {
|
|
93
|
+
Write-InstallStep "📥" "Installing dependencies..."
|
|
94
|
+
& npm install --silent
|
|
95
|
+
if ($LASTEXITCODE -ne 0) { throw "npm install failed." }
|
|
96
|
+
}
|
|
97
|
+
if (-not $SkipBuild) {
|
|
98
|
+
Write-InstallStep "🏗️" "Building shrinker..."
|
|
99
|
+
& npm run build --silent
|
|
100
|
+
if ($LASTEXITCODE -ne 0) { throw "npm run build failed." }
|
|
101
|
+
}
|
|
102
|
+
if (-not $SkipLink) {
|
|
103
|
+
Write-InstallStep "🔗" "Linking shrinker globally..."
|
|
104
|
+
& npm link --silent
|
|
105
|
+
if ($LASTEXITCODE -ne 0) { throw "npm link failed." }
|
|
106
|
+
}
|
|
107
|
+
} finally { Pop-Location }
|
|
108
|
+
|
|
109
|
+
if (-not $SkipProfile -and $EnableProfileRouting) { Add-ProfileIntegration $ProfilePath $integrationPath }
|
|
110
|
+
if (-not $SkipAgentRules) { Set-AgentRules $rulesBody }
|
|
111
|
+
Write-InstallStep "✅" "Install complete."
|
|
112
|
+
Write-Host " "
|
|
113
|
+
Write-Host "💡 Try: shrinker help"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[switch]$SkipUnlink,
|
|
3
|
+
[switch]$SkipAgentRules,
|
|
4
|
+
[switch]$CopilotOnly,
|
|
5
|
+
[switch]$ClaudeOnly,
|
|
6
|
+
[string]$ProfilePath = $PROFILE
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
$ErrorActionPreference = "Stop"
|
|
10
|
+
$UninstallStep = 0
|
|
11
|
+
|
|
12
|
+
function Write-UninstallStep {
|
|
13
|
+
param([string]$Emoji, [string]$Message)
|
|
14
|
+
$script:UninstallStep++
|
|
15
|
+
Write-Host "$($script:UninstallStep). $Emoji $Message"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function Remove-AgentRules {
|
|
19
|
+
param([string]$Path)
|
|
20
|
+
if (-not (Test-Path $Path)) { return }
|
|
21
|
+
$content = Get-Content $Path -Raw
|
|
22
|
+
$start = "<!-- shrinker agent rules start -->"
|
|
23
|
+
$end = "<!-- shrinker agent rules end -->"
|
|
24
|
+
if ($null -ne $content -and $content.Contains($start) -and $content.Contains($end)) {
|
|
25
|
+
$pattern = [regex]::Escape($start) + ".*?" + [regex]::Escape($end)
|
|
26
|
+
Set-Content $Path ([regex]::Replace($content, $pattern, "", 'Singleline').TrimEnd() + "`n")
|
|
27
|
+
Write-UninstallStep "📄" "Removed managed rules from: $Path"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function Remove-ProfileIntegration {
|
|
32
|
+
param([string]$ProfileFile)
|
|
33
|
+
if (-not (Test-Path $ProfileFile)) { return }
|
|
34
|
+
$content = Get-Content $ProfileFile -Raw
|
|
35
|
+
if ($null -eq $content) { return }
|
|
36
|
+
$start = $content.IndexOf("# >>> shrinker integration >>>")
|
|
37
|
+
$endMarker = "# <<< shrinker integration <<<"
|
|
38
|
+
if ($start -ge 0) {
|
|
39
|
+
$end = $content.IndexOf($endMarker, $start)
|
|
40
|
+
if ($end -ge 0) { Set-Content $ProfileFile $content.Remove($start, $end + $endMarker.Length - $start) }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (-not $SkipUnlink) {
|
|
45
|
+
Write-UninstallStep "🔗" "Unlinking shrinker globally..."
|
|
46
|
+
& npm unlink --silent --global shrinker-ai
|
|
47
|
+
if ($LASTEXITCODE -ne 0) { throw "npm unlink failed." }
|
|
48
|
+
}
|
|
49
|
+
else { Write-UninstallStep "⏭️" "Skipped global npm unlink." }
|
|
50
|
+
Write-UninstallStep "🔧" "Removing PowerShell profile integration..."
|
|
51
|
+
Remove-ProfileIntegration $ProfilePath
|
|
52
|
+
if (-not $SkipAgentRules) {
|
|
53
|
+
if (-not $ClaudeOnly) { Remove-AgentRules (Join-Path $HOME ".copilot\copilot-instructions.md") }
|
|
54
|
+
if (-not $CopilotOnly) { Remove-AgentRules (Join-Path $HOME ".claude\CLAUDE.md") }
|
|
55
|
+
}
|
|
56
|
+
else { Write-UninstallStep "⏭️" "Skipped managed agent rules." }
|
|
57
|
+
Write-UninstallStep "✅" "Uninstall complete."
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shrinker-ai",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Local deterministic command-output shrinker for coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"shrinker": "
|
|
7
|
+
"shrinker": "dist/src/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "tsc -w -p tsconfig.json",
|
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
"pack": "npm run build --silent && npm pack",
|
|
13
13
|
"test": "npm run build --silent && node --test \"dist/tests/*.test.js\"",
|
|
14
14
|
"demo": "npm run build --silent && node dist/demo/run-demo.js",
|
|
15
|
-
"install:local": "pwsh -ExecutionPolicy Bypass -File ./integrations/install
|
|
16
|
-
"
|
|
17
|
-
"install:
|
|
18
|
-
"
|
|
15
|
+
"install:local": "pwsh -ExecutionPolicy Bypass -File ./integrations/windows/install.ps1 -Local",
|
|
16
|
+
"install:local:macos": "./integrations/macos/install.sh --local",
|
|
17
|
+
"install:github": "pwsh -ExecutionPolicy Bypass -File ./integrations/windows/install.ps1",
|
|
18
|
+
"install:github:macos": "./integrations/macos/install.sh",
|
|
19
|
+
"uninstall:local": "pwsh -ExecutionPolicy Bypass -File ./integrations/windows/uninstall.ps1",
|
|
20
|
+
"uninstall:local:macos": "./integrations/macos/uninstall.sh",
|
|
21
|
+
"uninstall:github": "pwsh -ExecutionPolicy Bypass -File ./integrations/windows/uninstall.ps1",
|
|
22
|
+
"uninstall:github:macos": "./integrations/macos/uninstall.sh"
|
|
19
23
|
},
|
|
20
24
|
"devDependencies": {
|
|
21
25
|
"@types/node": "^24.0.0",
|
|
@@ -27,5 +31,9 @@
|
|
|
27
31
|
"publishConfig": {
|
|
28
32
|
"registry": "https://registry.npmjs.org"
|
|
29
33
|
},
|
|
30
|
-
"license": "MIT"
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/ivanduplenskikh/shrinker.git"
|
|
38
|
+
}
|
|
31
39
|
}
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[switch]$SkipNpmInstall,
|
|
3
|
-
[switch]$SkipBuild,
|
|
4
|
-
[switch]$SkipLink,
|
|
5
|
-
[switch]$EnableProfileRouting,
|
|
6
|
-
[switch]$SkipProfile,
|
|
7
|
-
[switch]$SkipAgentRules,
|
|
8
|
-
[switch]$CopilotOnly,
|
|
9
|
-
[switch]$ClaudeOnly,
|
|
10
|
-
[string]$ProfilePath = $PROFILE
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
$ErrorActionPreference = "Stop"
|
|
14
|
-
|
|
15
|
-
if ($EnableProfileRouting -and $SkipProfile) {
|
|
16
|
-
throw "Use either -EnableProfileRouting or -SkipProfile, not both."
|
|
17
|
-
}
|
|
18
|
-
if ($CopilotOnly -and $ClaudeOnly) {
|
|
19
|
-
throw "Use either -CopilotOnly or -ClaudeOnly, not both."
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
$blockStart = "<!-- shrinker agent rules start -->"
|
|
23
|
-
$blockEnd = "<!-- shrinker agent rules end -->"
|
|
24
|
-
|
|
25
|
-
function Set-AgentRules {
|
|
26
|
-
param(
|
|
27
|
-
[string]$RepoRoot,
|
|
28
|
-
[string]$Body
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
$targets = @()
|
|
32
|
-
if (-not $ClaudeOnly) { $targets += (Join-Path $RepoRoot ".copilot-instructions.md") }
|
|
33
|
-
if (-not $CopilotOnly) { $targets += (Join-Path $RepoRoot "CLAUDE.md") }
|
|
34
|
-
|
|
35
|
-
foreach ($target in $targets) {
|
|
36
|
-
$content = if (Test-Path $target) { Get-Content -Path $target -Raw } else { "" }
|
|
37
|
-
$newBlock = "$blockStart`n$Body`n$blockEnd"
|
|
38
|
-
if ($content -and $content.Contains($blockStart) -and $content.Contains($blockEnd)) {
|
|
39
|
-
$pattern = [regex]::Escape($blockStart) + ".*?" + [regex]::Escape($blockEnd)
|
|
40
|
-
$content = [regex]::Replace($content, $pattern, $newBlock, [System.Text.RegularExpressions.RegexOptions]::Singleline)
|
|
41
|
-
Set-Content -Path $target -Value $content
|
|
42
|
-
} elseif ($content) {
|
|
43
|
-
Add-Content -Path $target -Value "`n$newBlock`n"
|
|
44
|
-
} else {
|
|
45
|
-
Set-Content -Path $target -Value "$newBlock`n"
|
|
46
|
-
}
|
|
47
|
-
Write-Host "Installed managed rules in: $target"
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function Test-NodeVersion {
|
|
52
|
-
$versionText = & node -v 2>$null
|
|
53
|
-
if (-not $versionText) {
|
|
54
|
-
throw "Node.js was not found on PATH. Install Node.js 22.13+ first."
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
$match = [regex]::Match($versionText, "^v(\d+)\.(\d+)\.(\d+)$")
|
|
58
|
-
if (-not $match.Success) {
|
|
59
|
-
throw "Could not parse Node.js version: $versionText"
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
$major = [int]$match.Groups[1].Value
|
|
63
|
-
$minor = [int]$match.Groups[2].Value
|
|
64
|
-
$patch = [int]$match.Groups[3].Value
|
|
65
|
-
|
|
66
|
-
if ($major -lt 22 -or ($major -eq 22 -and $minor -lt 13)) {
|
|
67
|
-
throw "Node.js 22.13+ is required. Found $versionText"
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return $versionText
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function Add-ProfileIntegration {
|
|
74
|
-
param(
|
|
75
|
-
[string]$ProfileFile,
|
|
76
|
-
[string]$IntegrationFile
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
if (-not (Test-Path $ProfileFile)) {
|
|
80
|
-
New-Item -ItemType File -Path $ProfileFile -Force | Out-Null
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
$startMarker = "# >>> shrinker integration >>>"
|
|
84
|
-
$endMarker = "# <<< shrinker integration <<<"
|
|
85
|
-
|
|
86
|
-
$existing = Get-Content -Path $ProfileFile -Raw -ErrorAction SilentlyContinue
|
|
87
|
-
if ($existing -and $existing.Contains($startMarker)) {
|
|
88
|
-
Write-Host "Profile already contains shrinker integration block: $ProfileFile"
|
|
89
|
-
return
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
$block = @"
|
|
93
|
-
$startMarker
|
|
94
|
-
. "$IntegrationFile"
|
|
95
|
-
$endMarker
|
|
96
|
-
"@
|
|
97
|
-
|
|
98
|
-
Add-Content -Path $ProfileFile -Value "`n$block`n"
|
|
99
|
-
Write-Host "Added shrinker integration block to profile: $ProfileFile"
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
103
|
-
$templatePath = Join-Path $scriptDir "..\templates\agent-rules.md"
|
|
104
|
-
$repoRoot = Resolve-Path (Join-Path $scriptDir "..")
|
|
105
|
-
$integrationPath = (Resolve-Path (Join-Path $scriptDir "shrinker-profile.ps1")).Path
|
|
106
|
-
|
|
107
|
-
if (-not $SkipAgentRules) {
|
|
108
|
-
if (-not (Test-Path $templatePath)) {
|
|
109
|
-
throw "Agent rules template not found: $templatePath"
|
|
110
|
-
}
|
|
111
|
-
$rulesBody = Get-Content -Path $templatePath -Raw
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
Write-Host "Installing shrinker from: $repoRoot"
|
|
115
|
-
$nodeVersion = Test-NodeVersion
|
|
116
|
-
Write-Host "Detected Node.js: $nodeVersion"
|
|
117
|
-
|
|
118
|
-
Push-Location $repoRoot
|
|
119
|
-
try {
|
|
120
|
-
if (-not $SkipNpmInstall) {
|
|
121
|
-
Write-Host "Running npm install..."
|
|
122
|
-
& npm install
|
|
123
|
-
if ($LASTEXITCODE -ne 0) {
|
|
124
|
-
throw "npm install failed with exit code $LASTEXITCODE"
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (-not $SkipBuild) {
|
|
129
|
-
Write-Host "Running npm run build..."
|
|
130
|
-
& npm run build --silent
|
|
131
|
-
if ($LASTEXITCODE -ne 0) {
|
|
132
|
-
throw "npm run build failed with exit code $LASTEXITCODE"
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (-not $SkipLink) {
|
|
137
|
-
Write-Host "Running npm link..."
|
|
138
|
-
& npm link
|
|
139
|
-
if ($LASTEXITCODE -ne 0) {
|
|
140
|
-
throw "npm link failed with exit code $LASTEXITCODE"
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
finally {
|
|
145
|
-
Pop-Location
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (-not $SkipProfile) {
|
|
149
|
-
if ($EnableProfileRouting) {
|
|
150
|
-
Add-ProfileIntegration -ProfileFile $ProfilePath -IntegrationFile $integrationPath
|
|
151
|
-
Write-Host "Reload your profile with: . `$PROFILE"
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
Write-Host "Profile routing not enabled (default). Native commands remain unchanged."
|
|
155
|
-
Write-Host "To enable routing later: pwsh -ExecutionPolicy Bypass -File .\integrations\install-shrinker.ps1 -SkipNpmInstall -SkipBuild -SkipLink -EnableProfileRouting"
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
Write-Host "Profile routing skipped via -SkipProfile. Native commands remain unchanged."
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (-not $SkipAgentRules) {
|
|
163
|
-
Set-AgentRules -RepoRoot (Get-Location).Path -Body $rulesBody
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
Write-Host "Install complete. Try: shrinker help"
|
package/integrations/install.ps1
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[string]$PackageName = "shrinker-ai",
|
|
3
|
-
[string]$Registry = "https://registry.npmjs.org",
|
|
4
|
-
[string]$Version,
|
|
5
|
-
[switch]$EnableProfileRouting,
|
|
6
|
-
[switch]$SkipAgentRules,
|
|
7
|
-
[switch]$CopilotOnly,
|
|
8
|
-
[switch]$ClaudeOnly
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
$ErrorActionPreference = "Stop"
|
|
12
|
-
|
|
13
|
-
if ($CopilotOnly -and $ClaudeOnly) {
|
|
14
|
-
throw "Use either -CopilotOnly or -ClaudeOnly, not both."
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
$packageSpec = if ($Version) { "$PackageName@$Version" } else { $PackageName }
|
|
18
|
-
Write-Host "Installing $packageSpec from $Registry..."
|
|
19
|
-
& npm install --global $packageSpec "--registry=$Registry"
|
|
20
|
-
if ($LASTEXITCODE -ne 0) {
|
|
21
|
-
throw "npm package installation failed with exit code $LASTEXITCODE"
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
$globalRoot = (& npm root --global).Trim()
|
|
25
|
-
if (-not $globalRoot) {
|
|
26
|
-
throw "Could not determine the global npm package directory."
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
$packageRoot = Join-Path $globalRoot ($PackageName -replace '/', '\')
|
|
30
|
-
$localInstaller = Join-Path $packageRoot "integrations\install-shrinker.ps1"
|
|
31
|
-
if (-not (Test-Path $localInstaller)) {
|
|
32
|
-
throw "Installed package integration not found: $localInstaller"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
$localArgs = @("-ExecutionPolicy", "Bypass", "-File", $localInstaller, "-SkipNpmInstall", "-SkipBuild", "-SkipLink")
|
|
36
|
-
if ($EnableProfileRouting) { $localArgs += "-EnableProfileRouting" }
|
|
37
|
-
if ($SkipAgentRules) { $localArgs += "-SkipAgentRules" }
|
|
38
|
-
if ($CopilotOnly) { $localArgs += "-CopilotOnly" }
|
|
39
|
-
if ($ClaudeOnly) { $localArgs += "-ClaudeOnly" }
|
|
40
|
-
|
|
41
|
-
& pwsh @localArgs
|
|
42
|
-
if ($LASTEXITCODE -ne 0) {
|
|
43
|
-
throw "Installed package integration failed with exit code $LASTEXITCODE"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
Write-Host "Package installation complete. Try: shrinker help"
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[switch]$SkipUnlink,
|
|
3
|
-
[switch]$SkipAgentRules,
|
|
4
|
-
[switch]$CopilotOnly,
|
|
5
|
-
[switch]$ClaudeOnly,
|
|
6
|
-
[string]$ProfilePath = $PROFILE
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
$ErrorActionPreference = "Stop"
|
|
10
|
-
|
|
11
|
-
if ($CopilotOnly -and $ClaudeOnly) {
|
|
12
|
-
throw "Use either -CopilotOnly or -ClaudeOnly, not both."
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
$blockStart = "<!-- shrinker agent rules start -->"
|
|
16
|
-
$blockEnd = "<!-- shrinker agent rules end -->"
|
|
17
|
-
|
|
18
|
-
function Remove-AgentRules {
|
|
19
|
-
param([string]$Path)
|
|
20
|
-
|
|
21
|
-
if (-not (Test-Path $Path)) { return }
|
|
22
|
-
$content = Get-Content -Path $Path -Raw
|
|
23
|
-
if (-not ($content.Contains($blockStart) -and $content.Contains($blockEnd))) { return }
|
|
24
|
-
$pattern = [regex]::Escape($blockStart) + ".*?" + [regex]::Escape($blockEnd)
|
|
25
|
-
Set-Content -Path $Path -Value ([regex]::Replace($content, $pattern, "", [System.Text.RegularExpressions.RegexOptions]::Singleline).TrimEnd() + "`n")
|
|
26
|
-
Write-Host "Removed managed rules from: $Path"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function Remove-ProfileIntegration {
|
|
30
|
-
param([string]$ProfileFile)
|
|
31
|
-
|
|
32
|
-
if (-not (Test-Path $ProfileFile)) {
|
|
33
|
-
Write-Host "Profile not found: $ProfileFile"
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
$startMarker = "# >>> shrinker integration >>>"
|
|
38
|
-
$endMarker = "# <<< shrinker integration <<<"
|
|
39
|
-
$content = Get-Content -Path $ProfileFile -Raw
|
|
40
|
-
if ($null -eq $content -or $content.Length -eq 0) {
|
|
41
|
-
Write-Host "Profile is empty; nothing to remove."
|
|
42
|
-
return
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
$start = $content.IndexOf($startMarker)
|
|
46
|
-
if ($start -lt 0) {
|
|
47
|
-
Write-Host "No shrinker integration block found in profile."
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
$end = $content.IndexOf($endMarker, $start)
|
|
52
|
-
if ($end -lt 0) {
|
|
53
|
-
Write-Host "Integration block start found but end marker is missing; leaving profile unchanged."
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
$end += $endMarker.Length
|
|
58
|
-
$updated = $content.Remove($start, $end - $start)
|
|
59
|
-
Set-Content -Path $ProfileFile -Value $updated
|
|
60
|
-
Write-Host "Removed shrinker integration block from: $ProfileFile"
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (-not $SkipUnlink) {
|
|
64
|
-
Write-Host "Running npm unlink -g shrinker..."
|
|
65
|
-
& npm unlink -g shrinker
|
|
66
|
-
if ($LASTEXITCODE -ne 0) {
|
|
67
|
-
throw "npm unlink failed with exit code $LASTEXITCODE"
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
Remove-ProfileIntegration -ProfileFile $ProfilePath
|
|
72
|
-
Write-Host "If this terminal previously loaded shrinker-profile.ps1, restart terminal or remove Function:git/Function:rg wrappers from current session."
|
|
73
|
-
|
|
74
|
-
if (-not $SkipAgentRules) {
|
|
75
|
-
if (-not $ClaudeOnly) { Remove-AgentRules -Path (Join-Path (Get-Location).Path ".copilot-instructions.md") }
|
|
76
|
-
if (-not $CopilotOnly) { Remove-AgentRules -Path (Join-Path (Get-Location).Path "CLAUDE.md") }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
Write-Host "Uninstall complete."
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
param(
|
|
2
|
-
[string]$PackageName = "shrinker-ai",
|
|
3
|
-
[string]$Registry = "https://registry.npmjs.org",
|
|
4
|
-
[switch]$SkipAgentRules,
|
|
5
|
-
[switch]$CopilotOnly,
|
|
6
|
-
[switch]$ClaudeOnly
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
$ErrorActionPreference = "Stop"
|
|
10
|
-
|
|
11
|
-
if ($CopilotOnly -and $ClaudeOnly) {
|
|
12
|
-
throw "Use either -CopilotOnly or -ClaudeOnly, not both."
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
$globalRoot = (& npm root --global).Trim()
|
|
16
|
-
$packageRoot = Join-Path $globalRoot ($PackageName -replace '/', '\')
|
|
17
|
-
$localUninstaller = Join-Path $packageRoot "integrations\uninstall-shrinker.ps1"
|
|
18
|
-
if (Test-Path $localUninstaller) {
|
|
19
|
-
$localArgs = @("-ExecutionPolicy", "Bypass", "-File", $localUninstaller, "-SkipUnlink")
|
|
20
|
-
if ($SkipAgentRules) { $localArgs += "-SkipAgentRules" }
|
|
21
|
-
if ($CopilotOnly) { $localArgs += "-CopilotOnly" }
|
|
22
|
-
if ($ClaudeOnly) { $localArgs += "-ClaudeOnly" }
|
|
23
|
-
& pwsh @localArgs
|
|
24
|
-
if ($LASTEXITCODE -ne 0) {
|
|
25
|
-
throw "Installed package integration failed with exit code $LASTEXITCODE"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
Write-Host "Removing $PackageName..."
|
|
30
|
-
& npm uninstall --global $PackageName "--registry=$Registry"
|
|
31
|
-
if ($LASTEXITCODE -ne 0) {
|
|
32
|
-
throw "npm package removal failed with exit code $LASTEXITCODE"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
Write-Host "Package uninstallation complete."
|
|
File without changes
|