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,190 @@
|
|
|
1
|
+
"""Synchronous IPC client for PowerShell adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import shlex
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Ensure project root on path when invoked as a script
|
|
13
|
+
_ROOT = Path(__file__).resolve().parents[1]
|
|
14
|
+
if str(_ROOT) not in sys.path:
|
|
15
|
+
sys.path.insert(0, str(_ROOT))
|
|
16
|
+
|
|
17
|
+
from shellmate.ipc.client import ShellMateClient
|
|
18
|
+
|
|
19
|
+
# Common subcommand / flag completions for offline / fast path (no daemon required)
|
|
20
|
+
_SUBCOMMANDS = {
|
|
21
|
+
"git": [
|
|
22
|
+
"status", "push", "pull", "commit", "checkout", "branch", "log", "diff",
|
|
23
|
+
"add", "merge", "rebase", "reset", "fetch", "clone", "stash", "remote",
|
|
24
|
+
],
|
|
25
|
+
"docker": ["ps", "images", "run", "build", "exec", "stop", "rm", "compose", "logs"],
|
|
26
|
+
"kubectl": ["get", "apply", "delete", "describe", "logs", "exec", "port-forward"],
|
|
27
|
+
"npm": ["install", "run", "test", "build", "start", "ci"],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_FLAGS = {
|
|
31
|
+
"git": ["--force", "--all", "--verbose", "--help", "--amend", "--no-verify"],
|
|
32
|
+
"docker": ["--help", "--version", "--verbose", "--rm", "-it"],
|
|
33
|
+
"kubectl": ["--namespace", "--context", "--kubeconfig", "--help"],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _derive_completions(partial: str, program: str, result: dict) -> list:
|
|
38
|
+
"""Derive completions from analysis output for partial commands."""
|
|
39
|
+
completions: list[str] = []
|
|
40
|
+
risk = result.get("risk", "")
|
|
41
|
+
if risk in ("HIGH", "CRITICAL"):
|
|
42
|
+
rem = result.get("remediation") or {}
|
|
43
|
+
if isinstance(rem, dict):
|
|
44
|
+
alts = rem.get("alternatives") or []
|
|
45
|
+
completions.extend([a for a in alts if isinstance(a, str)][:3])
|
|
46
|
+
guide = result.get("command_guide") or {}
|
|
47
|
+
if isinstance(guide, dict):
|
|
48
|
+
completions.extend(
|
|
49
|
+
[s for s in (guide.get("suggestions") or []) if isinstance(s, str)][:2]
|
|
50
|
+
)
|
|
51
|
+
elif isinstance(guide, list):
|
|
52
|
+
completions.extend([s for s in guide if isinstance(s, str)][:5])
|
|
53
|
+
return completions[:5]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _local_completions(partial: str) -> list[str]:
|
|
57
|
+
"""Fast local completions for ghost-text / Tab (works without daemon)."""
|
|
58
|
+
partial = (partial or "").strip()
|
|
59
|
+
if not partial:
|
|
60
|
+
return []
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
tokens = shlex.split(partial)
|
|
64
|
+
except ValueError:
|
|
65
|
+
tokens = partial.split()
|
|
66
|
+
|
|
67
|
+
if not tokens:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
program = tokens[0].lower()
|
|
71
|
+
# Trailing space => user finished a token; suggest next word from empty prefix
|
|
72
|
+
ends_with_space = partial.endswith(" ")
|
|
73
|
+
|
|
74
|
+
if len(tokens) == 1 and not ends_with_space:
|
|
75
|
+
# Completing the program name itself — leave to shell builtins
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
# Flag completion
|
|
79
|
+
current = "" if ends_with_space else tokens[-1]
|
|
80
|
+
if current.startswith("-"):
|
|
81
|
+
flags = _FLAGS.get(program, [])
|
|
82
|
+
return [f for f in flags if f.startswith(current)][:5]
|
|
83
|
+
|
|
84
|
+
# Subcommand completion (e.g. "git pu" -> "git push")
|
|
85
|
+
if program in _SUBCOMMANDS:
|
|
86
|
+
if len(tokens) == 1 and ends_with_space:
|
|
87
|
+
prefix = ""
|
|
88
|
+
base = program + " "
|
|
89
|
+
elif len(tokens) == 2 and not ends_with_space:
|
|
90
|
+
prefix = tokens[1]
|
|
91
|
+
base = program + " "
|
|
92
|
+
else:
|
|
93
|
+
# Deeper args: try importing bash flag helper / return empty
|
|
94
|
+
prefix = current
|
|
95
|
+
base = " ".join(tokens[:-1]) + " " if tokens else ""
|
|
96
|
+
try:
|
|
97
|
+
from shellmate.completion.bash_adapter import get_flag_completions
|
|
98
|
+
flags = get_flag_completions(program, prefix if prefix.startswith("-") else "-")
|
|
99
|
+
if prefix.startswith("-"):
|
|
100
|
+
return flags[:5]
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
if len(tokens) >= 2 and not ends_with_space and tokens[1] in _SUBCOMMANDS.get(program, []):
|
|
104
|
+
return []
|
|
105
|
+
# Still allow subcommand match when second token is partial
|
|
106
|
+
if len(tokens) == 2:
|
|
107
|
+
matches = [s for s in _SUBCOMMANDS[program] if s.startswith(prefix)]
|
|
108
|
+
return [f"{program} {m}" for m in matches][:5]
|
|
109
|
+
return []
|
|
110
|
+
|
|
111
|
+
matches = [s for s in _SUBCOMMANDS[program] if s.startswith(prefix)]
|
|
112
|
+
return [f"{base}{m}".rstrip() if base.endswith(" ") else f"{program} {m}" for m in matches][:5]
|
|
113
|
+
|
|
114
|
+
return []
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class PSReadLineSyncClient:
|
|
118
|
+
"""Blocking wrapper around async Python client for PowerShell."""
|
|
119
|
+
|
|
120
|
+
def send_command_partial_sync(self, command: str, timeout: float = 5.0) -> dict:
|
|
121
|
+
"""Blocking call to /analyze-partial endpoint."""
|
|
122
|
+
loop = asyncio.new_event_loop()
|
|
123
|
+
asyncio.set_event_loop(loop)
|
|
124
|
+
try:
|
|
125
|
+
client = ShellMateClient()
|
|
126
|
+
result = loop.run_until_complete(
|
|
127
|
+
asyncio.wait_for(client.send_command_partial(command), timeout=timeout)
|
|
128
|
+
)
|
|
129
|
+
return result
|
|
130
|
+
except Exception as e:
|
|
131
|
+
return {"status": "error", "error": str(e)}
|
|
132
|
+
finally:
|
|
133
|
+
loop.close()
|
|
134
|
+
|
|
135
|
+
def instant_check_sync(self, command: str) -> dict:
|
|
136
|
+
"""Local catastrophic match (no daemon required)."""
|
|
137
|
+
from shellmate.rules.catastrophic_matcher import match_catastrophic
|
|
138
|
+
return match_catastrophic(command)
|
|
139
|
+
|
|
140
|
+
def complete_sync(self, command: str, timeout: float = 0.2) -> dict:
|
|
141
|
+
"""Return completion suggestions for ghost text / Tab.
|
|
142
|
+
|
|
143
|
+
Prefers local completions (fast). Merges daemon-derived suggestions when available.
|
|
144
|
+
"""
|
|
145
|
+
local = _local_completions(command)
|
|
146
|
+
daemon_completions: list[str] = []
|
|
147
|
+
try:
|
|
148
|
+
result = self.send_command_partial_sync(command, timeout=timeout)
|
|
149
|
+
guide = result.get("command_guide") or result.get("annotation", "")
|
|
150
|
+
if isinstance(guide, list):
|
|
151
|
+
daemon_completions = [g for g in guide if isinstance(g, str)][:5]
|
|
152
|
+
elif result.get("status") == "ok" or result.get("annotation") or result.get("risk"):
|
|
153
|
+
program = ""
|
|
154
|
+
parsed = result.get("parsed") or {}
|
|
155
|
+
if isinstance(parsed, dict):
|
|
156
|
+
program = parsed.get("program") or ""
|
|
157
|
+
if not program:
|
|
158
|
+
try:
|
|
159
|
+
program = shlex.split(command)[0] if command.strip() else ""
|
|
160
|
+
except ValueError:
|
|
161
|
+
program = command.split()[0] if command.split() else ""
|
|
162
|
+
daemon_completions = _derive_completions(command, program, result)
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
# Dedupe preserving order; local first for latency/stability
|
|
167
|
+
seen = set()
|
|
168
|
+
merged: list[str] = []
|
|
169
|
+
for item in local + daemon_completions:
|
|
170
|
+
if item and item not in seen:
|
|
171
|
+
seen.add(item)
|
|
172
|
+
merged.append(item)
|
|
173
|
+
return {"completions": merged[:5]}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
parser = argparse.ArgumentParser(description="ShellMate PowerShell IPC client")
|
|
178
|
+
parser.add_argument("command", nargs="?", default="")
|
|
179
|
+
parser.add_argument("--complete", action="store_true", help="Return completions instead of analysis")
|
|
180
|
+
parser.add_argument("--instant", action="store_true", help="Local catastrophic check only")
|
|
181
|
+
args = parser.parse_args()
|
|
182
|
+
|
|
183
|
+
client = PSReadLineSyncClient()
|
|
184
|
+
if args.instant:
|
|
185
|
+
result = client.instant_check_sync(args.command)
|
|
186
|
+
elif args.complete:
|
|
187
|
+
result = client.complete_sync(args.command)
|
|
188
|
+
else:
|
|
189
|
+
result = client.send_command_partial_sync(args.command)
|
|
190
|
+
print(json.dumps(result))
|