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.
@@ -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