shellmate-cli 3.0.0__py3-none-any.whl
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.
- shellmate/__init__.py +0 -0
- shellmate/adapters/__init__.py +67 -0
- shellmate/adapters/bash_adapter.sh +7 -0
- shellmate/adapters/ble_adapter.sh +350 -0
- shellmate/adapters/powershell_adapter.ps1 +417 -0
- shellmate/adapters/powershell_predictor.ps1 +97 -0
- shellmate/adapters/zsh_adapter.zsh +8 -0
- shellmate/agents/__init__.py +23 -0
- shellmate/agents/base.py +248 -0
- shellmate/agents/chain_patterns.yaml +98 -0
- shellmate/agents/command_chain.py +322 -0
- shellmate/agents/context_gatherer.py +175 -0
- shellmate/agents/environment.py +363 -0
- shellmate/agents/librarian.py +502 -0
- shellmate/agents/query.py +415 -0
- shellmate/agents/repomix_backend.py +171 -0
- shellmate/agents/watcher.py +266 -0
- shellmate/cache/__init__.py +0 -0
- shellmate/cache/semantic_cache.py +41 -0
- shellmate/cli/__init__.py +0 -0
- shellmate/cli/commands/__init__.py +27 -0
- shellmate/cli/commands/analyze.py +5 -0
- shellmate/cli/commands/config.py +319 -0
- shellmate/cli/commands/configure.py +118 -0
- shellmate/cli/commands/disable.py +11 -0
- shellmate/cli/commands/enable.py +49 -0
- shellmate/cli/commands/init.py +412 -0
- shellmate/cli/commands/install.py +242 -0
- shellmate/cli/commands/migrate.py +147 -0
- shellmate/cli/commands/status.py +31 -0
- shellmate/cli/commands/uninstall.py +78 -0
- shellmate/cli/main.py +114 -0
- shellmate/cli/parser.py +184 -0
- shellmate/cli/version.py +1 -0
- shellmate/completion/__init__.py +4 -0
- shellmate/completion/bash_adapter.py +145 -0
- shellmate/config/__init__.py +33 -0
- shellmate/config/credentials.py +120 -0
- shellmate/config/defaults.py +88 -0
- shellmate/config/manager.py +94 -0
- shellmate/config/paths.py +77 -0
- shellmate/config/secrets.py +66 -0
- shellmate/daemon/__init__.py +0 -0
- shellmate/daemon/lifecycle.py +308 -0
- shellmate/daemon/observability.py +82 -0
- shellmate/daemon/pipeline.py +119 -0
- shellmate/daemon/server.py +605 -0
- shellmate/daemon/session.py +170 -0
- shellmate/daemon/startup_ui.py +43 -0
- shellmate/environment/__init__.py +0 -0
- shellmate/environment/detector.py +130 -0
- shellmate/ipc/__init__.py +0 -0
- shellmate/ipc/client.py +210 -0
- shellmate/ipc/ps_client.py +190 -0
- shellmate/orchestration/__init__.py +3 -0
- shellmate/orchestration/analysis_graph.py +1274 -0
- shellmate/orchestration/state.py +35 -0
- shellmate/parser/__init__.py +0 -0
- shellmate/parser/command_parser.py +111 -0
- shellmate/plugins/__init__.py +0 -0
- shellmate/plugins/base.py +80 -0
- shellmate/plugins/loader.py +41 -0
- shellmate/providers/__init__.py +0 -0
- shellmate/providers/base.py +97 -0
- shellmate/providers/groq_command_guide.py +108 -0
- shellmate/providers/groq_provider.py +127 -0
- shellmate/providers/groq_supervisor.py +78 -0
- shellmate/providers/openrouter_provider.py +149 -0
- shellmate/providers/router.py +272 -0
- shellmate/providers/validate_keys.py +98 -0
- shellmate/providers/xai_provider.py +142 -0
- shellmate/rules/__init__.py +0 -0
- shellmate/rules/catastrophic.yaml +96 -0
- shellmate/rules/catastrophic_matcher.py +167 -0
- shellmate/rules/definitions/__init__.py +0 -0
- shellmate/rules/definitions/file_deletion.yaml +54 -0
- shellmate/rules/definitions/filesystem.yaml +43 -0
- shellmate/rules/definitions/network_execution.yaml +46 -0
- shellmate/rules/definitions/permissions.yaml +54 -0
- shellmate/rules/definitions/process_control.yaml +37 -0
- shellmate/rules/definitions/system_destruction.yaml +67 -0
- shellmate/rules/engine.py +200 -0
- shellmate/sanitizer/__init__.py +0 -0
- shellmate/sanitizer/typo_detector.py +114 -0
- shellmate/ui/__init__.py +10 -0
- shellmate/ui/analysis.py +164 -0
- shellmate/ui/annotations.py +232 -0
- shellmate/ui/banner.py +49 -0
- shellmate/ui/progress.py +126 -0
- shellmate/ui/runtime/__init__.py +15 -0
- shellmate/ui/runtime/console_factory.py +65 -0
- shellmate/ui/runtime/live_scene.py +116 -0
- shellmate/ui/runtime/motion_policy.py +69 -0
- shellmate/ui/runtime/tokens.py +47 -0
- shellmate/ui/scenes/__init__.py +24 -0
- shellmate/ui/scenes/enable.py +316 -0
- shellmate/ui/scenes/help.py +53 -0
- shellmate/ui/scenes/stages.py +128 -0
- shellmate/ui/scenes/status.py +181 -0
- shellmate/ui/status.py +3 -0
- shellmate_cli-3.0.0.dist-info/METADATA +352 -0
- shellmate_cli-3.0.0.dist-info/RECORD +106 -0
- shellmate_cli-3.0.0.dist-info/WHEEL +5 -0
- shellmate_cli-3.0.0.dist-info/entry_points.txt +3 -0
- shellmate_cli-3.0.0.dist-info/licenses/LICENSE +21 -0
- shellmate_cli-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# ShellMate PowerShell Adapter - PSReadLine two-phase annotations
|
|
2
|
+
# Install: add to $PROFILE -> . <path-to>/adapters/powershell_adapter.ps1
|
|
3
|
+
# Encoding: ASCII-safe strings for Windows PowerShell 5.1 compatibility
|
|
4
|
+
|
|
5
|
+
$script:ShellMateRoot = Split-Path -Parent $PSScriptRoot
|
|
6
|
+
if (-not $script:ShellMateRoot) {
|
|
7
|
+
$script:ShellMateRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
8
|
+
$script:ShellMateRoot = Split-Path -Parent $script:ShellMateRoot
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
$script:SMTimer = $null
|
|
12
|
+
$script:ShellMateLastDangerous = $null
|
|
13
|
+
$script:ShellMateNeedsConfirm = $null
|
|
14
|
+
$script:ShellMatePython = $null
|
|
15
|
+
$script:SMConsequenceShown = $null # command for which consequence was shown
|
|
16
|
+
$script:SMConsequenceRequireConfirm = $null # command needing second Enter
|
|
17
|
+
$script:SMConsequenceConfirmArmed = $null # set after first Enter blocked
|
|
18
|
+
$script:SMConsequenceJob = $null # background job fetching consequence
|
|
19
|
+
|
|
20
|
+
function Get-ShellMatePython {
|
|
21
|
+
if ($script:ShellMatePython) { return $script:ShellMatePython }
|
|
22
|
+
$candidates = @("python", "python3", "py")
|
|
23
|
+
foreach ($c in $candidates) {
|
|
24
|
+
$cmd = Get-Command $c -ErrorAction SilentlyContinue
|
|
25
|
+
if ($cmd) {
|
|
26
|
+
$script:ShellMatePython = $cmd.Source
|
|
27
|
+
return $script:ShellMatePython
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return $null
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Show-ShellMateAnnotation {
|
|
34
|
+
param(
|
|
35
|
+
[string]$Text,
|
|
36
|
+
[string]$Severity = "MEDIUM"
|
|
37
|
+
)
|
|
38
|
+
if ([string]::IsNullOrWhiteSpace($Text)) { return }
|
|
39
|
+
|
|
40
|
+
$ESC = [char]27
|
|
41
|
+
$color = switch ($Severity) {
|
|
42
|
+
"CRITICAL" { "${ESC}[91m" }
|
|
43
|
+
"HIGH" { "${ESC}[91m" }
|
|
44
|
+
"blocking" { "${ESC}[91m" }
|
|
45
|
+
default { "${ESC}[93m" }
|
|
46
|
+
}
|
|
47
|
+
$reset = "${ESC}[0m"
|
|
48
|
+
|
|
49
|
+
# Fallback for legacy consoles without VT
|
|
50
|
+
if (-not $env:TERM -and $PSVersionTable.PSVersion.Major -le 5) {
|
|
51
|
+
Write-Host ""
|
|
52
|
+
Write-Host $Text -ForegroundColor Red
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# Save cursor, move down 1, clear line, write, restore - target <2ms
|
|
57
|
+
[Console]::Write("${ESC}[s${ESC}[1B${ESC}[2K${color}${Text}${reset}${ESC}[u")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function Show-ShellMateConsequence {
|
|
61
|
+
param(
|
|
62
|
+
[string]$WhatHappens,
|
|
63
|
+
[string]$Scope,
|
|
64
|
+
[string]$Reversible,
|
|
65
|
+
[string]$SuggestedFix = "",
|
|
66
|
+
[string]$Severity = "MEDIUM"
|
|
67
|
+
)
|
|
68
|
+
$ESC = [char]27
|
|
69
|
+
$color = if ($Severity -in @("HIGH","CRITICAL","blocking")) { "${ESC}[91m" } `
|
|
70
|
+
elseif ($Severity -in @("SAFE","LOW")) { "${ESC}[92m" } `
|
|
71
|
+
else { "${ESC}[93m" }
|
|
72
|
+
$reset = "${ESC}[0m"
|
|
73
|
+
$dim = "${ESC}[90m"
|
|
74
|
+
|
|
75
|
+
$line1 = "${color} -> ${WhatHappens}${reset}"
|
|
76
|
+
$line2 = "${color} -> Reversible: ${Reversible} | ${Scope}${reset}"
|
|
77
|
+
$line3 = if ($SuggestedFix) { "${dim} -> Safe alt: ${SuggestedFix}${reset}" } else { "" }
|
|
78
|
+
|
|
79
|
+
# Save cursor, write 3 lines below prompt, restore
|
|
80
|
+
[Console]::Write("${ESC}[s")
|
|
81
|
+
[Console]::Write("${ESC}[1B${ESC}[2K${line1}")
|
|
82
|
+
[Console]::Write("${ESC}[2B${ESC}[2K${line2}")
|
|
83
|
+
if ($line3) { [Console]::Write("${ESC}[3B${ESC}[2K${line3}") }
|
|
84
|
+
[Console]::Write("${ESC}[u")
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function Clear-ShellMateConsequence {
|
|
88
|
+
$ESC = [char]27
|
|
89
|
+
[Console]::Write("${ESC}[s${ESC}[1B${ESC}[2K${ESC}[2B${ESC}[2K${ESC}[3B${ESC}[2K${ESC}[u")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function Invoke-ShellMateConsequenceAsync {
|
|
93
|
+
param([string]$Command)
|
|
94
|
+
|
|
95
|
+
# Show placeholder immediately
|
|
96
|
+
$ESC = [char]27
|
|
97
|
+
[Console]::Write("${ESC}[s${ESC}[1B${ESC}[2K${ESC}[90m [?] Analyzing...${ESC}[0m${ESC}[u")
|
|
98
|
+
|
|
99
|
+
# Stop any previous consequence job
|
|
100
|
+
if ($script:SMConsequenceJob) {
|
|
101
|
+
Stop-Job $script:SMConsequenceJob -ErrorAction SilentlyContinue
|
|
102
|
+
Remove-Job $script:SMConsequenceJob -Force -ErrorAction SilentlyContinue
|
|
103
|
+
$script:SMConsequenceJob = $null
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
$py = Get-ShellMatePython
|
|
107
|
+
$client = Get-ShellMateClientPath
|
|
108
|
+
$capturedCmd = $Command
|
|
109
|
+
|
|
110
|
+
$script:SMConsequenceJob = Start-Job -ScriptBlock {
|
|
111
|
+
param($py, $client, $cmd)
|
|
112
|
+
try {
|
|
113
|
+
$raw = & $py $client $cmd 2>$null | Out-String
|
|
114
|
+
if ($raw) { return ($raw | ConvertFrom-Json) }
|
|
115
|
+
} catch {}
|
|
116
|
+
return $null
|
|
117
|
+
} -ArgumentList $py, $client, $capturedCmd
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function Invoke-ShellMateInstantCheck {
|
|
121
|
+
param([string]$Command)
|
|
122
|
+
$py = Get-ShellMatePython
|
|
123
|
+
if (-not $py) { return $null }
|
|
124
|
+
|
|
125
|
+
$matcher = Join-Path $script:ShellMateRoot "rules\catastrophic_matcher.py"
|
|
126
|
+
if (-not (Test-Path $matcher)) {
|
|
127
|
+
$matcher = Join-Path $PSScriptRoot "..\rules\catastrophic_matcher.py"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
$json = & $py $matcher $Command 2>$null | Out-String
|
|
132
|
+
if ($json) {
|
|
133
|
+
return ($json | ConvertFrom-Json)
|
|
134
|
+
}
|
|
135
|
+
} catch {}
|
|
136
|
+
return $null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function Start-ShellMateDebounce {
|
|
140
|
+
param([string]$Command)
|
|
141
|
+
|
|
142
|
+
if ($script:SMTimer) {
|
|
143
|
+
$script:SMTimer.Stop()
|
|
144
|
+
$script:SMTimer.Dispose()
|
|
145
|
+
$script:SMTimer = $null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
$py = Get-ShellMatePython
|
|
149
|
+
if (-not $py) { return }
|
|
150
|
+
|
|
151
|
+
$client = Join-Path $script:ShellMateRoot "ipc\ps_client.py"
|
|
152
|
+
if (-not (Test-Path $client)) {
|
|
153
|
+
$client = Join-Path $PSScriptRoot "..\ipc\ps_client.py"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
$script:SMTimer = [System.Timers.Timer]::new(300)
|
|
157
|
+
$script:SMTimer.AutoReset = $false
|
|
158
|
+
$capturedCmd = $Command
|
|
159
|
+
$capturedPy = $py
|
|
160
|
+
$capturedClient = $client
|
|
161
|
+
$script:SMTimer.add_Elapsed({
|
|
162
|
+
try {
|
|
163
|
+
$raw = & $capturedPy $capturedClient $capturedCmd 2>$null | Out-String
|
|
164
|
+
if ($raw) {
|
|
165
|
+
$result = $raw | ConvertFrom-Json
|
|
166
|
+
if ($result.annotation) {
|
|
167
|
+
$sev = if ($result.severity) { $result.severity } else { "MEDIUM" }
|
|
168
|
+
Show-ShellMateAnnotation -Text $result.annotation -Severity $sev
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {}
|
|
172
|
+
}.GetNewClosure())
|
|
173
|
+
$script:SMTimer.Start()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function Stop-ShellMateDebounce {
|
|
177
|
+
if ($script:SMTimer) {
|
|
178
|
+
$script:SMTimer.Stop()
|
|
179
|
+
$script:SMTimer.Dispose()
|
|
180
|
+
$script:SMTimer = $null
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Enter key interception - Tier 1 instant block, Tier 2 accept + debounce
|
|
185
|
+
try {
|
|
186
|
+
Set-PSReadLineKeyHandler -Key Enter -ScriptBlock {
|
|
187
|
+
param($key, $arg)
|
|
188
|
+
|
|
189
|
+
$command = $null
|
|
190
|
+
$cursor = $null
|
|
191
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$cursor)
|
|
192
|
+
|
|
193
|
+
if ([string]::IsNullOrWhiteSpace($command)) {
|
|
194
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# Tier 1: Instant catastrophic check
|
|
199
|
+
$instant = Invoke-ShellMateInstantCheck -Command $command
|
|
200
|
+
if ($instant -and $instant.matched) {
|
|
201
|
+
$msg = if ($instant.message) { $instant.message } else { "BLOCKING - dangerous command" }
|
|
202
|
+
Show-ShellMateAnnotation -Text ("[!] BLOCKING - " + $msg) -Severity blocking
|
|
203
|
+
|
|
204
|
+
# Double-tap confirm: first Enter blocks, second allows
|
|
205
|
+
if ($script:ShellMateLastDangerous -ne $command) {
|
|
206
|
+
$script:ShellMateLastDangerous = $command
|
|
207
|
+
$script:ShellMateNeedsConfirm = $command
|
|
208
|
+
return # Suppress Enter
|
|
209
|
+
}
|
|
210
|
+
# Second Enter - allow through
|
|
211
|
+
$script:ShellMateLastDangerous = $null
|
|
212
|
+
$script:ShellMateNeedsConfirm = $null
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
Stop-ShellMateDebounce
|
|
216
|
+
|
|
217
|
+
# Consequence gate: HIGH/CRITICAL commands need second Enter after preview
|
|
218
|
+
if ($script:SMConsequenceRequireConfirm -and $script:SMConsequenceRequireConfirm -eq $command) {
|
|
219
|
+
if ($script:SMConsequenceConfirmArmed -ne $command) {
|
|
220
|
+
$script:SMConsequenceConfirmArmed = $command
|
|
221
|
+
Show-ShellMateAnnotation -Text "[Press Enter again to execute]" -Severity blocking
|
|
222
|
+
return # Suppress first Enter
|
|
223
|
+
}
|
|
224
|
+
$script:SMConsequenceRequireConfirm = $null
|
|
225
|
+
$script:SMConsequenceConfirmArmed = $null
|
|
226
|
+
# Second Enter — allow execution
|
|
227
|
+
} else {
|
|
228
|
+
$script:SMConsequenceConfirmArmed = $null
|
|
229
|
+
}
|
|
230
|
+
Clear-ShellMateConsequence
|
|
231
|
+
$script:SMConsequenceShown = $null
|
|
232
|
+
|
|
233
|
+
Start-ShellMateDebounce $command
|
|
234
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
Write-Verbose "ShellMate: PSReadLine Enter hook unavailable: $_"
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Also expose analysis helper for non-PSReadLine hosts
|
|
241
|
+
function Invoke-ShellMateAnalysis {
|
|
242
|
+
param([string]$Command)
|
|
243
|
+
if ([string]::IsNullOrWhiteSpace($Command)) { return }
|
|
244
|
+
|
|
245
|
+
$instant = Invoke-ShellMateInstantCheck -Command $Command
|
|
246
|
+
if ($instant -and $instant.matched) {
|
|
247
|
+
Show-ShellMateAnnotation -Text ("[!] BLOCKING - " + $instant.message) -Severity blocking
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
$py = Get-ShellMatePython
|
|
252
|
+
$client = Join-Path $script:ShellMateRoot "ipc\ps_client.py"
|
|
253
|
+
if ($py -and (Test-Path $client)) {
|
|
254
|
+
try {
|
|
255
|
+
$raw = & $py $client $Command 2>$null | Out-String
|
|
256
|
+
$result = $raw | ConvertFrom-Json
|
|
257
|
+
if ($result.annotation) {
|
|
258
|
+
Show-ShellMateAnnotation -Text $result.annotation -Severity $result.severity
|
|
259
|
+
}
|
|
260
|
+
} catch {}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function Get-ShellMateClientPath {
|
|
265
|
+
$client = Join-Path $script:ShellMateRoot "ipc\ps_client.py"
|
|
266
|
+
if (-not (Test-Path $client)) {
|
|
267
|
+
$client = Join-Path $PSScriptRoot "..\ipc\ps_client.py"
|
|
268
|
+
}
|
|
269
|
+
return $client
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function Invoke-ShellMateComplete {
|
|
273
|
+
param([string]$Prefix)
|
|
274
|
+
$py = Get-ShellMatePython
|
|
275
|
+
$client = Get-ShellMateClientPath
|
|
276
|
+
if (-not $py -or -not (Test-Path $client)) { return @() }
|
|
277
|
+
try {
|
|
278
|
+
$raw = & $py $client "--complete" $Prefix 2>$null | Out-String
|
|
279
|
+
if (-not $raw) { return @() }
|
|
280
|
+
$result = $raw | ConvertFrom-Json
|
|
281
|
+
if ($result.completions) { return @($result.completions) }
|
|
282
|
+
} catch {}
|
|
283
|
+
return @()
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# --- Step 3.5: Ghost text (PS 7.2+ predictor) or Tab fallback (PS 5.1) ---
|
|
287
|
+
$script:ShellMatePredictorRegistered = $false
|
|
288
|
+
$script:SMLastSuggestion = $null
|
|
289
|
+
$script:SMLastPrefix = $null
|
|
290
|
+
|
|
291
|
+
$psrl = Get-Module PSReadLine -ErrorAction SilentlyContinue
|
|
292
|
+
$canPredict = (
|
|
293
|
+
$PSVersionTable.PSVersion -ge [Version]"7.2" -and
|
|
294
|
+
$null -ne $psrl -and
|
|
295
|
+
$psrl.Version -ge [Version]"2.1"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if ($canPredict) {
|
|
299
|
+
$predictorFile = Join-Path $PSScriptRoot "powershell_predictor.ps1"
|
|
300
|
+
if (Test-Path $predictorFile) {
|
|
301
|
+
# Guard against predictor load/compile failures (e.g. prediction-API
|
|
302
|
+
# drift across PS builds) so the adapter still loads and the Tab
|
|
303
|
+
# ghost-text fallback below can engage instead of spewing errors.
|
|
304
|
+
try { . $predictorFile } catch { Write-Verbose "ShellMate: predictor load failed: $_" }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
# Path B: Tab override ghost-text when predictor is unavailable
|
|
309
|
+
if (-not $script:ShellMatePredictorRegistered) {
|
|
310
|
+
try {
|
|
311
|
+
Set-PSReadLineKeyHandler -Key Tab -ScriptBlock {
|
|
312
|
+
param($key, $arg)
|
|
313
|
+
$line = $null; $cursor = $null
|
|
314
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
|
|
315
|
+
if ($null -eq $line) { $line = "" }
|
|
316
|
+
$prefix = if ($cursor -gt 0 -and $cursor -le $line.Length) { $line.Substring(0, $cursor) } else { $line }
|
|
317
|
+
|
|
318
|
+
# Second Tab: suggestion cached → accept + show consequence
|
|
319
|
+
if ($script:SMLastPrefix -eq $prefix -and $script:SMLastSuggestion) {
|
|
320
|
+
$accepted = $script:SMLastSuggestion
|
|
321
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, $accepted)
|
|
322
|
+
$script:SMLastSuggestion = $null
|
|
323
|
+
$script:SMLastPrefix = $null
|
|
324
|
+
$script:SMConsequenceShown = $accepted
|
|
325
|
+
|
|
326
|
+
# Fetch consequence in background
|
|
327
|
+
Invoke-ShellMateConsequenceAsync -Command $accepted
|
|
328
|
+
|
|
329
|
+
# Poll job for up to 1.5s then render
|
|
330
|
+
$deadline = [DateTime]::Now.AddSeconds(1.5)
|
|
331
|
+
while ([DateTime]::Now -lt $deadline) {
|
|
332
|
+
Start-Sleep -Milliseconds 80
|
|
333
|
+
if ($script:SMConsequenceJob -and $script:SMConsequenceJob.State -ne 'Running') {
|
|
334
|
+
$result = Receive-Job $script:SMConsequenceJob -ErrorAction SilentlyContinue
|
|
335
|
+
Remove-Job $script:SMConsequenceJob -Force -ErrorAction SilentlyContinue
|
|
336
|
+
$script:SMConsequenceJob = $null
|
|
337
|
+
if ($result) {
|
|
338
|
+
$cp = $result.consequence_preview
|
|
339
|
+
if ($cp) {
|
|
340
|
+
$rev = if ($cp.reversible) { "Yes" } else { "No" }
|
|
341
|
+
$sev = if ($result.severity) { $result.severity } else { "MEDIUM" }
|
|
342
|
+
Show-ShellMateConsequence `
|
|
343
|
+
-WhatHappens $cp.what_happens `
|
|
344
|
+
-Scope $cp.scope `
|
|
345
|
+
-Reversible $rev `
|
|
346
|
+
-SuggestedFix $cp.suggested_fix `
|
|
347
|
+
-Severity $sev
|
|
348
|
+
if ($sev -in @("HIGH","CRITICAL","blocking")) {
|
|
349
|
+
$script:SMConsequenceRequireConfirm = $accepted
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
break
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
# First Tab: get completions, show ghost text
|
|
360
|
+
$script:SMLastSuggestion = $null
|
|
361
|
+
$script:SMLastPrefix = $null
|
|
362
|
+
try {
|
|
363
|
+
$completions = @(Invoke-ShellMateComplete -Prefix $prefix)
|
|
364
|
+
$suggestion = $completions | Select-Object -First 1
|
|
365
|
+
if ($suggestion -and $suggestion -ne $prefix) {
|
|
366
|
+
$script:SMLastSuggestion = [string]$suggestion
|
|
367
|
+
$script:SMLastPrefix = $prefix
|
|
368
|
+
$ESC = [char]27
|
|
369
|
+
$suffix = ([string]$suggestion).Substring([Math]::Min($prefix.Length, ([string]$suggestion).Length))
|
|
370
|
+
if ($suffix) {
|
|
371
|
+
[Console]::Write("${ESC}[90m${suffix}${ESC}[0m")
|
|
372
|
+
[Console]::Write("${ESC}[$($suffix.Length)D")
|
|
373
|
+
}
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
} catch {}
|
|
377
|
+
[Microsoft.PowerShell.PSConsoleReadLine]::MenuComplete()
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
Write-Verbose "ShellMate: Tab ghost-text handler unavailable: $_"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
# --- Step 3.6: Native argument completer (optional augment) ---
|
|
385
|
+
# Requires -CommandName; bare -Native prompts interactively in PS and hangs sourcing.
|
|
386
|
+
# Tab handler + predictor already cover ShellMate completions — skip global native
|
|
387
|
+
# registration unless a curated command list is provided via env.
|
|
388
|
+
$smNativeCmds = $env:SM_NATIVE_COMPLETERS
|
|
389
|
+
if ($smNativeCmds) {
|
|
390
|
+
$names = @($smNativeCmds -split '[,; ]+' | Where-Object { $_ })
|
|
391
|
+
if ($names.Count -gt 0) {
|
|
392
|
+
try {
|
|
393
|
+
Register-ArgumentCompleter -Native -CommandName $names -ScriptBlock {
|
|
394
|
+
param(
|
|
395
|
+
[string]$wordToComplete,
|
|
396
|
+
[System.Management.Automation.Language.CommandAst]$commandAst,
|
|
397
|
+
[int]$cursorPosition
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
$fullLine = $commandAst.Extent.Text
|
|
401
|
+
try {
|
|
402
|
+
$completions = @(Invoke-ShellMateComplete -Prefix $fullLine)
|
|
403
|
+
foreach ($c in $completions) {
|
|
404
|
+
[System.Management.Automation.CompletionResult]::new(
|
|
405
|
+
[string]$c,
|
|
406
|
+
[string]$c,
|
|
407
|
+
"ParameterValue",
|
|
408
|
+
("ShellMate: " + [string]$c)
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
} catch {}
|
|
412
|
+
}
|
|
413
|
+
} catch {
|
|
414
|
+
Write-Verbose "ShellMate: ArgumentCompleter registration unavailable: $_"
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ShellMate PowerShell Prediction Source (PS 7.2+ / PSReadLine 2.1+)
|
|
2
|
+
# Sourced only when version checks pass — keeps powershell_adapter.ps1 parseable on 5.1
|
|
3
|
+
|
|
4
|
+
class ShellMatePredictor : System.Management.Automation.Subsystem.Prediction.ICommandPredictor {
|
|
5
|
+
[System.Guid] $Id = [System.Guid]::new("d8f1b5a2-4c3e-4f7d-9a8b-1e2f3c4d5e6f")
|
|
6
|
+
[string] $Name = "ShellMate"
|
|
7
|
+
[string] $Description = "Context-aware command suggestions"
|
|
8
|
+
[string] $PythonPath
|
|
9
|
+
[string] $ClientPath
|
|
10
|
+
|
|
11
|
+
ShellMatePredictor([string]$PythonPath, [string]$ClientPath) {
|
|
12
|
+
$this.PythonPath = $PythonPath
|
|
13
|
+
$this.ClientPath = $ClientPath
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
[bool] CanAcceptFeedback(
|
|
17
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
18
|
+
[System.Management.Automation.Subsystem.Prediction.PredictorFeedbackKind] $feedback
|
|
19
|
+
) { return $false }
|
|
20
|
+
|
|
21
|
+
[void] OnSuggestionDisplayed(
|
|
22
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
23
|
+
[uint] $session,
|
|
24
|
+
[int] $countOrIndex
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
[void] OnSuggestionAccepted(
|
|
28
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
29
|
+
[uint] $session,
|
|
30
|
+
[string] $acceptedSuggestion
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
[void] OnCommandLineAccepted(
|
|
34
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
35
|
+
[System.Collections.Generic.IReadOnlyList[string]] $history
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
[void] OnCommandLineExecuted(
|
|
39
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
40
|
+
[string] $commandLine,
|
|
41
|
+
[bool] $success
|
|
42
|
+
) {}
|
|
43
|
+
|
|
44
|
+
[System.Management.Automation.Subsystem.Prediction.SuggestionPackage] GetSuggestion(
|
|
45
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionClient] $client,
|
|
46
|
+
[System.Management.Automation.Subsystem.Prediction.PredictionContext] $context,
|
|
47
|
+
[System.Threading.CancellationToken] $cancellationToken
|
|
48
|
+
) {
|
|
49
|
+
$input = $context.InputAst.Extent.Text
|
|
50
|
+
if ([string]::IsNullOrWhiteSpace($input) -or $input.Length -lt 3) {
|
|
51
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new(@())
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
if (-not $this.PythonPath -or -not (Test-Path $this.ClientPath)) {
|
|
56
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new(@())
|
|
57
|
+
}
|
|
58
|
+
$raw = & $this.PythonPath $this.ClientPath "--complete" $input 2>$null | Out-String
|
|
59
|
+
if (-not $raw) {
|
|
60
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new(@())
|
|
61
|
+
}
|
|
62
|
+
$result = $raw | ConvertFrom-Json
|
|
63
|
+
if (-not $result.completions) {
|
|
64
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new(@())
|
|
65
|
+
}
|
|
66
|
+
$suggestions = @(
|
|
67
|
+
$result.completions | ForEach-Object {
|
|
68
|
+
[System.Management.Automation.Subsystem.Prediction.PredictiveSuggestion]::new([string]$_)
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new($suggestions)
|
|
72
|
+
} catch {
|
|
73
|
+
return [System.Management.Automation.Subsystem.Prediction.SuggestionPackage]::new(@())
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$py = Get-ShellMatePython
|
|
79
|
+
$client = Join-Path $script:ShellMateRoot "ipc\ps_client.py"
|
|
80
|
+
if (-not (Test-Path $client)) {
|
|
81
|
+
$client = Join-Path $PSScriptRoot "..\ipc\ps_client.py"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if ($py -and (Test-Path $client)) {
|
|
85
|
+
try {
|
|
86
|
+
$predictor = [ShellMatePredictor]::new($py, $client)
|
|
87
|
+
[System.Management.Automation.Subsystem.SubsystemManager]::RegisterSubsystem(
|
|
88
|
+
[System.Management.Automation.Subsystem.SubsystemKind]::CommandPredictor,
|
|
89
|
+
$predictor
|
|
90
|
+
)
|
|
91
|
+
Set-PSReadLineOption -PredictionSource Plugin -PredictionViewStyle InlineView -ErrorAction SilentlyContinue
|
|
92
|
+
$script:ShellMatePredictorRegistered = $true
|
|
93
|
+
} catch {
|
|
94
|
+
Write-Verbose "ShellMate: predictor registration failed: $_"
|
|
95
|
+
$script:ShellMatePredictorRegistered = $false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/bin/zsh
|
|
2
|
+
# ShellMate Zsh Adapter - Deprecated
|
|
3
|
+
# Interception mode has been removed - use bash with ble.sh for autocomplete mode
|
|
4
|
+
# This file is kept for backward compatibility only
|
|
5
|
+
|
|
6
|
+
echo "⚠️ ShellMate: zsh interception mode is deprecated."
|
|
7
|
+
echo " For autocomplete mode, use bash with ble.sh"
|
|
8
|
+
echo " Run: sm migrate-to-autocomplete"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from shellmate.agents.base import (
|
|
2
|
+
Agent,
|
|
3
|
+
AgentRequest,
|
|
4
|
+
AgentResponse,
|
|
5
|
+
AgentStatus,
|
|
6
|
+
AgentManager,
|
|
7
|
+
get_agent_manager,
|
|
8
|
+
)
|
|
9
|
+
from shellmate.agents.watcher import WatcherAgent, get_watcher_agent
|
|
10
|
+
from shellmate.agents.librarian import LibrarianAgent, get_librarian_agent
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Agent",
|
|
14
|
+
"AgentRequest",
|
|
15
|
+
"AgentResponse",
|
|
16
|
+
"AgentStatus",
|
|
17
|
+
"AgentManager",
|
|
18
|
+
"get_agent_manager",
|
|
19
|
+
"WatcherAgent",
|
|
20
|
+
"get_watcher_agent",
|
|
21
|
+
"LibrarianAgent",
|
|
22
|
+
"get_librarian_agent",
|
|
23
|
+
]
|