shrinker-ai 0.3.3 → 0.7.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.
@@ -1,113 +1,239 @@
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
- [int]$StepOffset = 0
16
- )
17
-
18
- $ErrorActionPreference = "Stop"
19
- $InstallStep = $StepOffset
20
-
21
- function Write-InstallStep {
22
- param([string]$Emoji, [string]$Message)
23
- $script:InstallStep++
24
- Write-Host "$($script:InstallStep). $Emoji $Message"
25
- }
26
-
27
- if ($EnableProfileRouting -and $SkipProfile) { throw "Use either -EnableProfileRouting or -SkipProfile, not both." }
28
- if ($CopilotOnly -and $ClaudeOnly) { throw "Use either -CopilotOnly or -ClaudeOnly, not both." }
29
-
30
- function Set-AgentRules {
31
- param([string]$Body)
32
- $start = "<!-- shrinker agent rules start -->"
33
- $end = "<!-- shrinker agent rules end -->"
34
- foreach ($target in @((Join-Path $HOME ".copilot\copilot-instructions.md"), (Join-Path $HOME ".claude\CLAUDE.md"))) {
35
- if ($ClaudeOnly -and $target.EndsWith(".copilot-instructions.md")) { continue }
36
- if ($CopilotOnly -and $target.EndsWith("CLAUDE.md")) { continue }
37
- New-Item -ItemType Directory -Path (Split-Path $target) -Force | Out-Null
38
- $content = if (Test-Path $target) { Get-Content $target -Raw } else { "" }
39
- $block = "$start`n$Body`n$end"
40
- if ($null -ne $content -and $content.Contains($start) -and $content.Contains($end)) {
41
- $content = [regex]::Replace($content, [regex]::Escape($start) + ".*?" + [regex]::Escape($end), $block, 'Singleline')
42
- Set-Content $target $content
43
- } elseif ($content) { Add-Content $target "`n$block`n" }
44
- else { Set-Content $target "$block`n" }
45
- Write-InstallStep "📄" "Installed managed rules in: $target"
46
- }
47
- }
48
-
49
- function Add-ProfileIntegration {
50
- param([string]$ProfileFile, [string]$IntegrationFile)
51
- if (-not (Test-Path $ProfileFile)) { New-Item -ItemType File -Path $ProfileFile -Force | Out-Null }
52
- $start = "# >>> shrinker integration >>>"
53
- if ((Get-Content $ProfileFile -Raw).Contains($start)) { return }
54
- Add-Content $ProfileFile "`n$start`n. `"$IntegrationFile`"`n# <<< shrinker integration <<<`n"
55
- Write-InstallStep "🔧" "Added shrinker integration block to profile: $ProfileFile"
56
- }
57
-
58
- if (-not $Local) {
59
- $packageSpec = if ($Version) { "$PackageName@$Version" } else { $PackageName }
60
- Write-InstallStep "📦" "Installing $packageSpec from $Registry..."
61
- & npm install --silent --global $packageSpec "--registry=$Registry"
62
- if ($LASTEXITCODE -ne 0) { throw "npm package installation failed." }
63
- Write-InstallStep "🔎" "Locating the installed package..."
64
- $globalRoot = (& npm root --global).Trim()
65
- if (-not $globalRoot) { throw "Could not determine the global npm package directory." }
66
- $packageRoot = Join-Path $globalRoot ($PackageName -replace '/', '\')
67
- $entrypoint = Join-Path $packageRoot "integrations\windows\install.ps1"
68
- if (-not (Test-Path $entrypoint)) { throw "Installed package installer not found: $entrypoint" }
69
- Write-InstallStep "⚙️" "Configuring the installed package..."
70
- & pwsh -ExecutionPolicy Bypass -File $entrypoint -Local -SkipNpmInstall -SkipBuild -SkipLink -EnableProfileRouting:$EnableProfileRouting -SkipProfile:$SkipProfile -SkipAgentRules:$SkipAgentRules -CopilotOnly:$CopilotOnly -ClaudeOnly:$ClaudeOnly -ProfilePath $ProfilePath -StepOffset $InstallStep
71
- if ($LASTEXITCODE -ne 0) { throw "Repository installer failed with exit code $LASTEXITCODE" }
72
- return
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"
1
+ param(
2
+ [switch]$Local,
3
+ [switch]$UseNpm,
4
+ [string]$PackageName = "shrinker-ai",
5
+ [string]$Registry = "https://registry.npmjs.org",
6
+ [string]$Version,
7
+ [string]$ReleaseRepo = "ivanduplenskikh/shrinker",
8
+ [string]$AssetBaseUrl,
9
+ [string]$InstallDir = $(Join-Path $HOME ".shrinker"),
10
+ [switch]$SkipNpmInstall,
11
+ [switch]$SkipBuild,
12
+ [switch]$SkipLink,
13
+ [switch]$EnableProfileRouting,
14
+ [switch]$SkipProfile,
15
+ [switch]$SkipAgentRules,
16
+ [switch]$CopilotOnly,
17
+ [switch]$ClaudeOnly,
18
+ [switch]$EnableUncoveredTracking,
19
+ [switch]$DisableUncoveredTracking,
20
+ [string]$ProfilePath = $PROFILE,
21
+ [string]$ConfigPath = $(if ($env:SHRINKER_CONFIG_PATH) { $env:SHRINKER_CONFIG_PATH } else { Join-Path $HOME ".shrinker/config" }),
22
+ [int]$StepOffset = 0
23
+ )
24
+
25
+ $ErrorActionPreference = "Stop"
26
+ $InstallStep = $StepOffset
27
+
28
+ function Write-InstallStep {
29
+ param([string]$Emoji, [string]$Message)
30
+ $script:InstallStep++
31
+ Write-Host "$($script:InstallStep). $Emoji $Message"
32
+ }
33
+
34
+ if ($EnableProfileRouting -and $SkipProfile) { throw "Use either -EnableProfileRouting or -SkipProfile, not both." }
35
+ if ($CopilotOnly -and $ClaudeOnly) { throw "Use either -CopilotOnly or -ClaudeOnly, not both." }
36
+ if ($EnableUncoveredTracking -and $DisableUncoveredTracking) { throw "Use either -EnableUncoveredTracking or -DisableUncoveredTracking, not both." }
37
+
38
+ # Prompt only on a real console; non-interactive installs keep tracking on and leave the profile alone.
39
+ $IsInteractive = -not [System.Console]::IsInputRedirected -and $Host.UI.RawUI -ne $null
40
+
41
+ function Read-YesNo {
42
+ param([string]$Question, [bool]$Default)
43
+ $hint = if ($Default) { "[Y/n]" } else { "[y/N]" }
44
+ $reply = (Read-Host " $Question $hint").Trim().ToLowerInvariant()
45
+ switch ($reply) {
46
+ { $_ -in @("y", "yes") } { return $true }
47
+ { $_ -in @("n", "no") } { return $false }
48
+ default { return $Default }
49
+ }
50
+ }
51
+
52
+ if ($EnableUncoveredTracking) { $TrackUncovered = $true }
53
+ elseif ($DisableUncoveredTracking) { $TrackUncovered = $false }
54
+ elseif ($IsInteractive) {
55
+ Write-InstallStep "" "Track uncovered commands? Records which unfiltered commands cost the most tokens."
56
+ $TrackUncovered = Read-YesNo "Enable uncovered-command tracking?" $true
57
+ }
58
+ else { $TrackUncovered = $true }
59
+
60
+ if ($IsInteractive -and -not $SkipProfile -and -not $EnableProfileRouting) {
61
+ Write-InstallStep "❓" "Route wrapped commands through shrinker automatically? Adds a line to $ProfilePath."
62
+ $EnableProfileRouting = Read-YesNo "Enable automatic shell routing?" $false
63
+ }
64
+
65
+ function Set-ConfigValue {
66
+ param([string]$Key, [string]$Value)
67
+ $directory = Split-Path -Parent $ConfigPath
68
+ if ($directory -and -not (Test-Path -LiteralPath $directory)) { New-Item -ItemType Directory -Force -Path $directory | Out-Null }
69
+ $lines = @()
70
+ if (Test-Path -LiteralPath $ConfigPath) {
71
+ $lines = @(Get-Content -LiteralPath $ConfigPath | Where-Object { $_ -notmatch "^\s*$Key\s*=" })
72
+ }
73
+ $lines += "$Key=$Value"
74
+ Set-Content -LiteralPath $ConfigPath -Value $lines -Encoding utf8
75
+ Write-InstallStep "⚙️" "Set $Key=$Value in: $ConfigPath"
76
+ }
77
+
78
+ function Set-AgentRules {
79
+ param([string]$Body)
80
+ $start = "<!-- shrinker agent rules start -->"
81
+ $end = "<!-- shrinker agent rules end -->"
82
+ foreach ($target in @((Join-Path $HOME ".copilot\copilot-instructions.md"), (Join-Path $HOME ".claude\CLAUDE.md"))) {
83
+ if ($ClaudeOnly -and $target.EndsWith(".copilot-instructions.md")) { continue }
84
+ if ($CopilotOnly -and $target.EndsWith("CLAUDE.md")) { continue }
85
+ New-Item -ItemType Directory -Path (Split-Path $target) -Force | Out-Null
86
+ $content = if (Test-Path $target) { Get-Content $target -Raw } else { "" }
87
+ $block = "$start`n$Body`n$end"
88
+ if ($null -ne $content -and $content.Contains($start) -and $content.Contains($end)) {
89
+ $content = [regex]::Replace($content, [regex]::Escape($start) + ".*?" + [regex]::Escape($end), $block, 'Singleline')
90
+ Set-Content $target $content
91
+ } elseif ($content) { Add-Content $target "`n$block`n" }
92
+ else { Set-Content $target "$block`n" }
93
+ Write-InstallStep "📄" "Installed managed rules in: $target"
94
+ }
95
+ }
96
+
97
+ function Add-ProfileIntegration {
98
+ param([string]$ProfileFile, [string]$IntegrationFile)
99
+ if (-not (Test-Path $ProfileFile)) { New-Item -ItemType File -Path $ProfileFile -Force | Out-Null }
100
+ $start = "# >>> shrinker integration >>>"
101
+ if ((Get-Content $ProfileFile -Raw).Contains($start)) { return }
102
+ Add-Content $ProfileFile "`n$start`n. `"$IntegrationFile`"`n# <<< shrinker integration <<<`n"
103
+ Write-InstallStep "🔧" "Added shrinker integration block to profile: $ProfileFile"
104
+ }
105
+
106
+ function Add-UserPathEntry {
107
+ param([string]$Directory)
108
+ $currentUserPath = [Environment]::GetEnvironmentVariable("Path", "User")
109
+ $entries = @($currentUserPath -split [IO.Path]::PathSeparator | Where-Object { $_ })
110
+ if ($entries -notcontains $Directory) {
111
+ $newPath = (@($entries) + $Directory) -join [IO.Path]::PathSeparator
112
+ [Environment]::SetEnvironmentVariable("Path", $newPath, "User")
113
+ Write-InstallStep "🧭" "Added shrinker to the user PATH: $Directory"
114
+ }
115
+ $processEntries = @($env:Path -split [IO.Path]::PathSeparator | Where-Object { $_ })
116
+ if ($processEntries -notcontains $Directory) {
117
+ $env:Path = (@($processEntries) + $Directory) -join [IO.Path]::PathSeparator
118
+ }
119
+ }
120
+
121
+ function Get-ReleaseAssetUrl {
122
+ $assetName = "shrinker-win-x64.zip"
123
+ if ($AssetBaseUrl) { return "$($AssetBaseUrl.TrimEnd('/'))/$assetName" }
124
+ if ($Version) {
125
+ $tag = if ($Version.StartsWith("v")) { $Version } else { "v$Version" }
126
+ return "https://github.com/$ReleaseRepo/releases/download/$tag/$assetName"
127
+ }
128
+ return "https://github.com/$ReleaseRepo/releases/latest/download/$assetName"
129
+ }
130
+
131
+ function Install-ReleasePackage {
132
+ $assetUrl = Get-ReleaseAssetUrl
133
+ $archivePath = Join-Path ([IO.Path]::GetTempPath()) "shrinker-win-x64.zip"
134
+ $extractPath = Join-Path ([IO.Path]::GetTempPath()) ("shrinker-install-" + [guid]::NewGuid().ToString("N"))
135
+ try {
136
+ Write-InstallStep "📦" "Downloading shrinker from: $assetUrl"
137
+ Invoke-WebRequest -Uri $assetUrl -OutFile $archivePath -UseBasicParsing
138
+ New-Item -ItemType Directory -Force -Path $extractPath | Out-Null
139
+ Expand-Archive -LiteralPath $archivePath -DestinationPath $extractPath -Force
140
+
141
+ foreach ($name in @("bin", "integrations", "templates")) {
142
+ $source = Join-Path $extractPath $name
143
+ if (-not (Test-Path -LiteralPath $source)) { throw "Release archive is missing: $name" }
144
+ Copy-Item -LiteralPath $source -Destination $InstallDir -Recurse -Force
145
+ }
146
+ if (Test-Path -LiteralPath (Join-Path $extractPath "manifest.json")) {
147
+ Copy-Item -LiteralPath (Join-Path $extractPath "manifest.json") -Destination $InstallDir -Force
148
+ }
149
+ }
150
+ finally {
151
+ Remove-Item -LiteralPath $archivePath -Force -ErrorAction SilentlyContinue
152
+ Remove-Item -LiteralPath $extractPath -Recurse -Force -ErrorAction SilentlyContinue
153
+ }
154
+
155
+ $binPath = Join-Path $InstallDir "bin"
156
+ $exePath = Join-Path $binPath "shrinker.exe"
157
+ if (-not (Test-Path -LiteralPath $exePath)) { throw "Installed executable not found: $exePath" }
158
+ Add-UserPathEntry $binPath
159
+
160
+ $templatePath = Join-Path $InstallDir "templates\agent-rules.md"
161
+ $integrationPath = Join-Path $InstallDir "integrations\windows\shrinker-profile.ps1"
162
+ if (-not $SkipAgentRules) {
163
+ if (-not (Test-Path $templatePath)) { throw "Agent rules template not found: $templatePath" }
164
+ $rulesBody = Get-Content $templatePath -Raw
165
+ }
166
+
167
+ Set-ConfigValue "SHRINKER_TRACK_UNCOVERED" $(if ($TrackUncovered) { "1" } else { "0" })
168
+ if (-not $SkipProfile -and $EnableProfileRouting) { Add-ProfileIntegration $ProfilePath $integrationPath }
169
+ if (-not $SkipAgentRules) { Set-AgentRules $rulesBody }
170
+ Write-InstallStep "✅" "Install complete."
171
+ Write-Host " "
172
+ Write-Host "💡 Try: shrinker help"
173
+ }
174
+
175
+ if (-not $Local -and $UseNpm) {
176
+ $packageSpec = if ($Version) { "$PackageName@$Version" } else { $PackageName }
177
+ Write-InstallStep "📦" "Installing $packageSpec from $Registry..."
178
+ & npm install --silent --global $packageSpec "--registry=$Registry"
179
+ if ($LASTEXITCODE -ne 0) { throw "npm package installation failed." }
180
+ Write-InstallStep "🔎" "Locating the installed package..."
181
+ $globalRoot = (& npm root --global).Trim()
182
+ if (-not $globalRoot) { throw "Could not determine the global npm package directory." }
183
+ $packageRoot = Join-Path $globalRoot ($PackageName -replace '/', '\')
184
+ $entrypoint = Join-Path $packageRoot "integrations\windows\install.ps1"
185
+ if (-not (Test-Path $entrypoint)) { throw "Installed package installer not found: $entrypoint" }
186
+ Write-InstallStep "⚙️" "Configuring the installed package..."
187
+ & pwsh -ExecutionPolicy Bypass -File $entrypoint -Local -SkipNpmInstall -SkipBuild -SkipLink -EnableProfileRouting:$EnableProfileRouting -SkipProfile:$SkipProfile -SkipAgentRules:$SkipAgentRules -CopilotOnly:$CopilotOnly -ClaudeOnly:$ClaudeOnly -EnableUncoveredTracking:$TrackUncovered -DisableUncoveredTracking:(-not $TrackUncovered) -ProfilePath $ProfilePath -ConfigPath $ConfigPath -StepOffset $InstallStep
188
+ if ($LASTEXITCODE -ne 0) { throw "Repository installer failed with exit code $LASTEXITCODE" }
189
+ return
190
+ }
191
+
192
+ if (-not $Local) {
193
+ Install-ReleasePackage
194
+ return
195
+ }
196
+
197
+ $scriptDir = $PSScriptRoot
198
+ $repoRoot = (Resolve-Path (Join-Path $scriptDir "..\..")).Path
199
+ $templatePath = Join-Path $repoRoot "templates\agent-rules.md"
200
+ $integrationPath = (Resolve-Path (Join-Path $scriptDir "shrinker-profile.ps1")).Path
201
+ if (-not $SkipAgentRules) {
202
+ if (-not (Test-Path $templatePath)) { throw "Agent rules template not found: $templatePath" }
203
+ $rulesBody = Get-Content $templatePath -Raw
204
+ }
205
+
206
+ $versionText = & node -v 2>$null
207
+ if (-not $versionText) { throw "Node.js was not found on PATH. Install Node.js 22.13+ first." }
208
+ $version = [version]($versionText.TrimStart('v'))
209
+ if ($version -lt [version]'22.13.0') { throw "Node.js 22.13+ is required. Found $versionText" }
210
+
211
+ Push-Location $repoRoot
212
+ try {
213
+ Write-InstallStep "📦" "Installing shrinker from: $repoRoot"
214
+ if (-not $SkipNpmInstall) {
215
+ Write-InstallStep "📥" "Installing dependencies..."
216
+ & npm install --silent
217
+ if ($LASTEXITCODE -ne 0) { throw "npm install failed." }
218
+ }
219
+ if (-not $SkipBuild) {
220
+ Write-InstallStep "🏗️" "Building shrinker..."
221
+ $previousQuiet = $env:SHRINKER_BUILD_QUIET
222
+ $env:SHRINKER_BUILD_QUIET = "1"
223
+ try { & npm run build --silent }
224
+ finally { $env:SHRINKER_BUILD_QUIET = $previousQuiet }
225
+ if ($LASTEXITCODE -ne 0) { throw "npm run build failed." }
226
+ }
227
+ if (-not $SkipLink) {
228
+ Write-InstallStep "🔗" "Linking shrinker globally..."
229
+ & npm link --silent
230
+ if ($LASTEXITCODE -ne 0) { throw "npm link failed." }
231
+ }
232
+ } finally { Pop-Location }
233
+
234
+ Set-ConfigValue "SHRINKER_TRACK_UNCOVERED" $(if ($TrackUncovered) { "1" } else { "0" })
235
+ if (-not $SkipProfile -and $EnableProfileRouting) { Add-ProfileIntegration $ProfilePath $integrationPath }
236
+ if (-not $SkipAgentRules) { Set-AgentRules $rulesBody }
237
+ Write-InstallStep "✅" "Install complete."
238
+ Write-Host " "
239
+ Write-Host "💡 Try: shrinker help"