cloudshellgpt 1.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.
- cloudshellgpt/__init__.py +11 -0
- cloudshellgpt/audit.py +175 -0
- cloudshellgpt/bedrock_translator.py +525 -0
- cloudshellgpt/cli.py +612 -0
- cloudshellgpt/config.py +296 -0
- cloudshellgpt/cost.py +443 -0
- cloudshellgpt/executor.py +382 -0
- cloudshellgpt/formatter.py +328 -0
- cloudshellgpt/i18n.py +203 -0
- cloudshellgpt/intent.py +1080 -0
- cloudshellgpt/learning.py +969 -0
- cloudshellgpt/mcp_server.py +264 -0
- cloudshellgpt/safety.py +952 -0
- cloudshellgpt-1.0.0.dist-info/METADATA +426 -0
- cloudshellgpt-1.0.0.dist-info/RECORD +18 -0
- cloudshellgpt-1.0.0.dist-info/WHEEL +4 -0
- cloudshellgpt-1.0.0.dist-info/entry_points.txt +2 -0
- cloudshellgpt-1.0.0.dist-info/licenses/LICENSE +198 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""CloudShellGPT — AWS CLI that speaks your language.
|
|
2
|
+
|
|
3
|
+
A developer productivity tool that translates natural language into AWS operations,
|
|
4
|
+
powered by Amazon Bedrock (Claude 3.5 Sonnet). Built for the HACKATHONKIRO.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__author__ = "CloudShellGPT Team"
|
|
11
|
+
__license__ = "Apache-2.0"
|
cloudshellgpt/audit.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Audit logger — records all command intents and results for compliance and debugging."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import uuid
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from cloudshellgpt.executor import ExecutionResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuditLogger:
|
|
15
|
+
"""Logs command intents BEFORE execution and results AFTER.
|
|
16
|
+
|
|
17
|
+
Critical safety invariant: logging must happen BEFORE execution so that
|
|
18
|
+
even if the process crashes mid-execution, we have a record of what was
|
|
19
|
+
attempted.
|
|
20
|
+
|
|
21
|
+
Format: JSON Lines (one JSON object per line)
|
|
22
|
+
Default location: ~/.csgpt/audit.log
|
|
23
|
+
|
|
24
|
+
Never-crash guarantee: all public methods catch all exceptions internally
|
|
25
|
+
and never propagate them to the caller.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
DEFAULT_PATH = Path.home() / ".csgpt" / "audit.log"
|
|
29
|
+
|
|
30
|
+
def __init__(self, log_path: Path | None = None) -> None:
|
|
31
|
+
"""Initialize the audit logger.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
log_path: Custom path for the audit log file. Defaults to ~/.csgpt/audit.log.
|
|
35
|
+
"""
|
|
36
|
+
self.log_path = log_path or self.DEFAULT_PATH
|
|
37
|
+
try:
|
|
38
|
+
self.log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
except OSError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def log_before(
|
|
43
|
+
self,
|
|
44
|
+
intent: str,
|
|
45
|
+
command: str,
|
|
46
|
+
risk: str,
|
|
47
|
+
dry_run: bool,
|
|
48
|
+
) -> str | None:
|
|
49
|
+
"""Log a command intent BEFORE execution.
|
|
50
|
+
|
|
51
|
+
This is the primary safety mechanism: the audit entry is written before
|
|
52
|
+
the command runs, ensuring we have a record even if the process crashes.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
intent: The original natural language intent.
|
|
56
|
+
command: The AWS command about to be executed.
|
|
57
|
+
risk: Risk level (low/medium/high/critical).
|
|
58
|
+
dry_run: Whether this will be a dry-run execution.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A unique entry_id that can be used to correlate with log_after,
|
|
62
|
+
or None if logging failed silently.
|
|
63
|
+
"""
|
|
64
|
+
entry_id = uuid.uuid4().hex
|
|
65
|
+
entry: dict[str, object] = {
|
|
66
|
+
"entry_id": entry_id,
|
|
67
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
68
|
+
"phase": "before",
|
|
69
|
+
"intent": intent,
|
|
70
|
+
"command": command,
|
|
71
|
+
"risk_level": risk,
|
|
72
|
+
"dry_run": dry_run,
|
|
73
|
+
"user": os.environ.get("USER", os.environ.get("USERNAME", "unknown")),
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
with self.log_path.open("a", encoding="utf-8") as f:
|
|
78
|
+
f.write(json.dumps(entry) + "\n")
|
|
79
|
+
except OSError:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
return entry_id
|
|
83
|
+
|
|
84
|
+
def log_after(
|
|
85
|
+
self,
|
|
86
|
+
entry_id: str | None,
|
|
87
|
+
result: ExecutionResult,
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Log execution results AFTER a command completes.
|
|
90
|
+
|
|
91
|
+
This appends a second log line correlating with the pre-execution entry
|
|
92
|
+
via entry_id, recording the outcome.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
entry_id: The id returned by log_before (may be None if pre-log failed).
|
|
96
|
+
result: The execution result to record.
|
|
97
|
+
"""
|
|
98
|
+
entry: dict[str, object] = {
|
|
99
|
+
"entry_id": entry_id,
|
|
100
|
+
"timestamp": datetime.now(UTC).isoformat(),
|
|
101
|
+
"phase": "after",
|
|
102
|
+
"command": result.command,
|
|
103
|
+
"exit_code": result.exit_code,
|
|
104
|
+
"duration_ms": result.duration_ms,
|
|
105
|
+
"dry_run": result.dry_run,
|
|
106
|
+
"stdout_size": len(result.stdout),
|
|
107
|
+
"stderr": result.stderr if result.exit_code != 0 else None,
|
|
108
|
+
"error": result.error,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
with self.log_path.open("a", encoding="utf-8") as f:
|
|
113
|
+
f.write(json.dumps(entry) + "\n")
|
|
114
|
+
except OSError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
def log(
|
|
118
|
+
self,
|
|
119
|
+
intent: str,
|
|
120
|
+
command: str,
|
|
121
|
+
risk: str,
|
|
122
|
+
dry_run: bool,
|
|
123
|
+
result: ExecutionResult | None = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Convenience method: log intent and optionally a result in one call.
|
|
126
|
+
|
|
127
|
+
Backwards-compatible with callers that want a single log() call.
|
|
128
|
+
When result is None, only the pre-execution entry is written.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
intent: The original natural language intent.
|
|
132
|
+
command: The executed AWS command.
|
|
133
|
+
risk: Risk level (low/medium/high/critical).
|
|
134
|
+
dry_run: Whether this was a dry-run.
|
|
135
|
+
result: The execution result (optional, for post-execution logging).
|
|
136
|
+
"""
|
|
137
|
+
entry_id = self.log_before(intent, command, risk, dry_run)
|
|
138
|
+
if result is not None:
|
|
139
|
+
self.log_after(entry_id, result)
|
|
140
|
+
|
|
141
|
+
def tail(self, n: int = 10) -> list[dict[str, object]]:
|
|
142
|
+
"""Return the last N entries from the log.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
n: Number of entries to return. Defaults to 10.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
List of log entry dictionaries, most recent last.
|
|
149
|
+
"""
|
|
150
|
+
try:
|
|
151
|
+
if not self.log_path.exists():
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
entries: list[dict[str, object]] = []
|
|
155
|
+
with self.log_path.open("r", encoding="utf-8") as f:
|
|
156
|
+
for line in f:
|
|
157
|
+
try:
|
|
158
|
+
entries.append(json.loads(line))
|
|
159
|
+
except json.JSONDecodeError:
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
return entries[-n:]
|
|
163
|
+
except OSError:
|
|
164
|
+
return []
|
|
165
|
+
|
|
166
|
+
def clear(self) -> None:
|
|
167
|
+
"""Clear the audit log.
|
|
168
|
+
|
|
169
|
+
Silently ignores errors if the file cannot be deleted.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
if self.log_path.exists():
|
|
173
|
+
self.log_path.unlink()
|
|
174
|
+
except OSError:
|
|
175
|
+
pass
|