codetool-shell 0.1.1__py3-none-win_arm64.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.
Files changed (72) hide show
  1. codetool_shell/__init__.py +11 -0
  2. codetool_shell/api.py +59 -0
  3. codetool_shell/bin/windows-arm64/codetool-shell-rust.exe +0 -0
  4. codetool_shell/filters/__init__.py +14 -0
  5. codetool_shell/filters/build_compiler/__init__.py +7 -0
  6. codetool_shell/filters/build_compiler/detector.py +412 -0
  7. codetool_shell/filters/build_compiler/reducer.py +166 -0
  8. codetool_shell/filters/build_compiler/summary.py +617 -0
  9. codetool_shell/filters/ci_job_log/__init__.py +7 -0
  10. codetool_shell/filters/ci_job_log/detector.py +64 -0
  11. codetool_shell/filters/ci_job_log/reducer.py +99 -0
  12. codetool_shell/filters/ci_job_log/summary.py +243 -0
  13. codetool_shell/filters/diff/__init__.py +7 -0
  14. codetool_shell/filters/diff/detector.py +136 -0
  15. codetool_shell/filters/diff/reducer.py +308 -0
  16. codetool_shell/filters/generic_log/__init__.py +7 -0
  17. codetool_shell/filters/generic_log/detector.py +175 -0
  18. codetool_shell/filters/generic_log/reducer.py +99 -0
  19. codetool_shell/filters/generic_log/summary.py +161 -0
  20. codetool_shell/filters/git.py +514 -0
  21. codetool_shell/filters/html_cleanup/__init__.py +7 -0
  22. codetool_shell/filters/html_cleanup/detector.py +136 -0
  23. codetool_shell/filters/html_cleanup/reducer.py +27 -0
  24. codetool_shell/filters/html_cleanup/summary.py +422 -0
  25. codetool_shell/filters/json_payload/__init__.py +7 -0
  26. codetool_shell/filters/json_payload/detector.py +62 -0
  27. codetool_shell/filters/json_payload/reducer.py +81 -0
  28. codetool_shell/filters/json_payload/summary.py +233 -0
  29. codetool_shell/filters/listing/__init__.py +7 -0
  30. codetool_shell/filters/listing/detector.py +294 -0
  31. codetool_shell/filters/listing/reducer.py +30 -0
  32. codetool_shell/filters/log_template/__init__.py +7 -0
  33. codetool_shell/filters/log_template/constants.py +76 -0
  34. codetool_shell/filters/log_template/detector.py +331 -0
  35. codetool_shell/filters/log_template/reducer.py +78 -0
  36. codetool_shell/filters/log_template/template.py +280 -0
  37. codetool_shell/filters/log_template/types.py +21 -0
  38. codetool_shell/filters/opaque_payload/__init__.py +7 -0
  39. codetool_shell/filters/opaque_payload/detector.py +563 -0
  40. codetool_shell/filters/opaque_payload/reducer.py +142 -0
  41. codetool_shell/filters/opaque_payload/summary.py +61 -0
  42. codetool_shell/filters/package_manager/__init__.py +7 -0
  43. codetool_shell/filters/package_manager/detector.py +220 -0
  44. codetool_shell/filters/package_manager/reducer.py +110 -0
  45. codetool_shell/filters/package_manager/summary.py +172 -0
  46. codetool_shell/filters/pipeline.py +65 -0
  47. codetool_shell/filters/rg.py +250 -0
  48. codetool_shell/filters/system_output/__init__.py +7 -0
  49. codetool_shell/filters/system_output/detector.py +600 -0
  50. codetool_shell/filters/system_output/reducer.py +331 -0
  51. codetool_shell/filters/system_output/summary.py +164 -0
  52. codetool_shell/filters/table/__init__.py +7 -0
  53. codetool_shell/filters/table/detector.py +244 -0
  54. codetool_shell/filters/table/reducer.py +57 -0
  55. codetool_shell/filters/table/summary.py +37 -0
  56. codetool_shell/filters/test_runner/__init__.py +7 -0
  57. codetool_shell/filters/test_runner/ansi.py +80 -0
  58. codetool_shell/filters/test_runner/detector.py +409 -0
  59. codetool_shell/filters/test_runner/reducer.py +288 -0
  60. codetool_shell/filters/test_runner/summary.py +449 -0
  61. codetool_shell/filters/text.py +38 -0
  62. codetool_shell/filters/traceback/__init__.py +7 -0
  63. codetool_shell/filters/traceback/detector.py +209 -0
  64. codetool_shell/filters/traceback/reducer.py +141 -0
  65. codetool_shell/filters/traceback/summary.py +122 -0
  66. codetool_shell/filters/tree.py +59 -0
  67. codetool_shell/py.typed +0 -0
  68. codetool_shell/python_backend.py +38 -0
  69. codetool_shell/rust_backend.py +254 -0
  70. codetool_shell-0.1.1.dist-info/METADATA +152 -0
  71. codetool_shell-0.1.1.dist-info/RECORD +72 -0
  72. codetool_shell-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,331 @@
1
+ """Reduce high-confidence system command outputs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..text import join_preserving_final_newline, score, split_preserving_final_newline
6
+ from .detector import (
7
+ DfOutput,
8
+ DuOutput,
9
+ EnvOutput,
10
+ PingOutput,
11
+ PsOutput,
12
+ StatBlock,
13
+ SystemctlStatusOutput,
14
+ WcOutput,
15
+ parse_df_output,
16
+ parse_du_output,
17
+ parse_env_output,
18
+ parse_ping_output,
19
+ parse_ps_output,
20
+ parse_stat_blocks,
21
+ parse_systemctl_status_output,
22
+ parse_wc_output,
23
+ )
24
+ from .summary import (
25
+ SAFE_ENV_KEYS,
26
+ format_env_var,
27
+ is_opaque_value,
28
+ is_sensitive_key,
29
+ omitted_line,
30
+ redact_sensitive_text,
31
+ )
32
+
33
+ _HEAD_ROWS = 2
34
+ _TAIL_ROWS = 2
35
+ _DF_HIGH_USE_THRESHOLD = 85
36
+ _PS_HIGH_CPU_THRESHOLD = 10.0
37
+ _PS_HIGH_MEM_THRESHOLD = 5.0
38
+ _TOP_DU_ROWS = 5
39
+ _PING_EDGE_REPLIES = 2
40
+
41
+
42
+ def compress_system_output(text: str) -> str:
43
+ """Compress clear system command outputs, otherwise return ``text``."""
44
+
45
+ lines, final_newline = split_preserving_final_newline(text)
46
+ if not lines:
47
+ return text
48
+
49
+ for reducer in (
50
+ _reduce_env,
51
+ _reduce_df,
52
+ _reduce_ps,
53
+ _reduce_ping,
54
+ _reduce_systemctl,
55
+ _reduce_du,
56
+ _reduce_wc,
57
+ _reduce_stat,
58
+ ):
59
+ candidate_lines = reducer(lines)
60
+ if candidate_lines is None:
61
+ continue
62
+ candidate = join_preserving_final_newline(candidate_lines, final_newline)
63
+ if score(candidate) < score(text):
64
+ return candidate
65
+ return text
66
+
67
+
68
+ def _reduce_env(lines: list[str]) -> list[str] | None:
69
+ parsed = parse_env_output(lines)
70
+ if parsed is None:
71
+ return None
72
+
73
+ vars_by_key = {var.key: var.value for var in parsed.vars}
74
+ selected = [
75
+ (key, vars_by_key[key]) for key in SAFE_ENV_KEYS if key in vars_by_key
76
+ ]
77
+ if not selected:
78
+ return None
79
+
80
+ redacted = sum(
81
+ 1
82
+ for var in parsed.vars
83
+ if is_sensitive_key(var.key) or (var.key != "PATH" and is_opaque_value(var.value))
84
+ )
85
+ omitted = len(parsed.vars) - len(selected)
86
+ output = [
87
+ f"system env: vars={len(parsed.vars)} redacted={redacted} omitted={omitted}"
88
+ ]
89
+ output.extend(format_env_var(key, value) for key, value in selected)
90
+ if omitted > 0:
91
+ output.append(omitted_line(omitted, "env", "var"))
92
+ return output
93
+
94
+
95
+ def _reduce_df(lines: list[str]) -> list[str] | None:
96
+ parsed = parse_df_output(lines)
97
+ if parsed is None:
98
+ return None
99
+
100
+ high_indices = [
101
+ index
102
+ for index, row in enumerate(parsed.rows)
103
+ if row.usage >= _DF_HIGH_USE_THRESHOLD
104
+ ]
105
+ important_indices = [
106
+ index for index, row in enumerate(parsed.rows) if _is_important_mount(row.mount)
107
+ ]
108
+ selected = _edge_indices(len(parsed.rows))
109
+ selected.extend(high_indices)
110
+ selected.extend(important_indices)
111
+
112
+ output = [
113
+ f"df: filesystems={len(parsed.rows)} high_use={len(high_indices)}",
114
+ parsed.header,
115
+ ]
116
+ output.extend(
117
+ _selected_rows(
118
+ len(parsed.rows),
119
+ selected,
120
+ lambda index: parsed.rows[index].line,
121
+ "df",
122
+ )
123
+ )
124
+ return output
125
+
126
+
127
+ def _reduce_du(lines: list[str]) -> list[str] | None:
128
+ parsed = parse_du_output(lines)
129
+ if parsed is None:
130
+ return None
131
+
132
+ selected = _edge_indices(len(parsed.rows))
133
+ selected.extend(
134
+ index
135
+ for index, _row in sorted(
136
+ enumerate(parsed.rows),
137
+ key=lambda item: item[1].size,
138
+ reverse=True,
139
+ )[:_TOP_DU_ROWS]
140
+ )
141
+ selected.extend(index for index, row in enumerate(parsed.rows) if row.is_total)
142
+
143
+ output = [f"du: entries={len(parsed.rows)}"]
144
+ output.extend(
145
+ _selected_rows(
146
+ len(parsed.rows),
147
+ selected,
148
+ lambda index: parsed.rows[index].line,
149
+ "du",
150
+ )
151
+ )
152
+ return output
153
+
154
+
155
+ def _reduce_ps(lines: list[str]) -> list[str] | None:
156
+ parsed = parse_ps_output(lines)
157
+ if parsed is None:
158
+ return None
159
+
160
+ high_indices = [
161
+ index
162
+ for index, row in enumerate(parsed.rows)
163
+ if (row.cpu is not None and row.cpu >= _PS_HIGH_CPU_THRESHOLD)
164
+ or (row.mem is not None and row.mem >= _PS_HIGH_MEM_THRESHOLD)
165
+ ]
166
+ interesting_indices = [
167
+ index
168
+ for index, row in enumerate(parsed.rows)
169
+ if _is_interesting_command(row.command)
170
+ ]
171
+ selected = _edge_indices(len(parsed.rows))
172
+ selected.extend(high_indices)
173
+ selected.extend(interesting_indices)
174
+
175
+ output = [
176
+ f"ps: processes={len(parsed.rows)} high={len(high_indices)} interesting={len(interesting_indices)}",
177
+ parsed.header,
178
+ ]
179
+ output.extend(
180
+ _selected_rows(
181
+ len(parsed.rows),
182
+ selected,
183
+ lambda index: redact_sensitive_text(parsed.rows[index].line),
184
+ "ps",
185
+ )
186
+ )
187
+ return output
188
+
189
+
190
+ def _reduce_ping(lines: list[str]) -> list[str] | None:
191
+ parsed = parse_ping_output(lines)
192
+ if parsed is None:
193
+ return None
194
+
195
+ selected = [0]
196
+ selected.extend(parsed.reply_indices[:_PING_EDGE_REPLIES])
197
+ selected.extend(parsed.reply_indices[-_PING_EDGE_REPLIES:])
198
+ selected.extend(parsed.event_indices)
199
+ selected.extend(parsed.summary_indices)
200
+
201
+ output = [
202
+ f"ping: replies={len(parsed.reply_indices)} events={len(parsed.event_indices)}"
203
+ ]
204
+ output.extend(
205
+ _selected_rows(
206
+ len(parsed.lines),
207
+ selected,
208
+ lambda index: parsed.lines[index],
209
+ "ping",
210
+ noun="line",
211
+ )
212
+ )
213
+ return output
214
+
215
+
216
+ def _reduce_systemctl(lines: list[str]) -> list[str] | None:
217
+ parsed = parse_systemctl_status_output(lines)
218
+ if parsed is None:
219
+ return None
220
+
221
+ selected = [*parsed.core_indices, *parsed.alert_indices]
222
+ output = [
223
+ f"systemctl status: lines={len(parsed.lines)} alerts={len(parsed.alert_indices)}"
224
+ ]
225
+ output.extend(
226
+ _selected_rows(
227
+ len(parsed.lines),
228
+ selected,
229
+ lambda index: redact_sensitive_text(parsed.lines[index]),
230
+ "status",
231
+ noun="line",
232
+ )
233
+ )
234
+ return output
235
+
236
+
237
+ def _reduce_wc(lines: list[str]) -> list[str] | None:
238
+ parsed = parse_wc_output(lines)
239
+ if parsed is None:
240
+ return None
241
+
242
+ file_indices = [index for index, row in enumerate(parsed.rows) if not row.is_total]
243
+ selected = file_indices[:3] + file_indices[-3:] + [parsed.total_index]
244
+ output = [f"wc: files={len(file_indices)}"]
245
+ output.extend(
246
+ _selected_rows(
247
+ len(parsed.rows),
248
+ selected,
249
+ lambda index: parsed.rows[index].line,
250
+ "wc",
251
+ )
252
+ )
253
+ return output
254
+
255
+
256
+ def _reduce_stat(lines: list[str]) -> list[str] | None:
257
+ blocks = parse_stat_blocks(lines)
258
+ if blocks is None:
259
+ return None
260
+ output = [f"stat: files={len(blocks)}"]
261
+ output.extend(_format_stat_block(block) for block in blocks)
262
+ return output
263
+
264
+
265
+ def _selected_rows(
266
+ total: int,
267
+ selected: list[int],
268
+ line_for_index,
269
+ label: str,
270
+ *,
271
+ noun: str = "row",
272
+ ) -> list[str]:
273
+ output: list[str] = []
274
+ selected_sorted = sorted({index for index in selected if 0 <= index < total})
275
+ cursor = 0
276
+ for index in selected_sorted:
277
+ omitted = index - cursor
278
+ if omitted > 0:
279
+ output.append(omitted_line(omitted, label, noun))
280
+ output.append(line_for_index(index))
281
+ cursor = index + 1
282
+ if cursor < total:
283
+ output.append(omitted_line(total - cursor, label, noun))
284
+ return output
285
+
286
+
287
+ def _edge_indices(total: int) -> list[int]:
288
+ return [
289
+ *range(min(_HEAD_ROWS, total)),
290
+ *range(max(_HEAD_ROWS, total - _TAIL_ROWS), total),
291
+ ]
292
+
293
+
294
+ def _is_important_mount(mount: str) -> bool:
295
+ return mount in {"/", "/home", "/var", "/tmp", "/boot", "/data"} or mount.startswith(
296
+ ("/var/", "/boot/", "/data/")
297
+ )
298
+
299
+
300
+ def _is_interesting_command(command: str) -> bool:
301
+ lower = command.lower()
302
+ return any(
303
+ marker in lower
304
+ for marker in (
305
+ "python",
306
+ "pytest",
307
+ "cargo",
308
+ "rustc",
309
+ "node",
310
+ "npm",
311
+ "java",
312
+ "gradle",
313
+ "mvn",
314
+ "postgres",
315
+ "redis",
316
+ "nginx",
317
+ "gunicorn",
318
+ "uvicorn",
319
+ "docker",
320
+ "kubectl",
321
+ "systemd",
322
+ "sshd",
323
+ )
324
+ )
325
+
326
+
327
+ def _format_stat_block(block: StatBlock) -> str:
328
+ return (
329
+ f"{block.file} | size={block.size} | mode={block.mode} | "
330
+ f"owner={block.owner} | group={block.group} | modify={block.modify}"
331
+ )
@@ -0,0 +1,164 @@
1
+ """Formatting and redaction helpers for system-shellion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ SAFE_ENV_KEYS: tuple[str, ...] = (
8
+ "USER",
9
+ "LOGNAME",
10
+ "HOME",
11
+ "PWD",
12
+ "SHELL",
13
+ "TERM",
14
+ "LANG",
15
+ "LC_ALL",
16
+ "VIRTUAL_ENV",
17
+ "CONDA_DEFAULT_ENV",
18
+ "PYTHON_VERSION",
19
+ "PATH",
20
+ )
21
+
22
+ _SENSITIVE_KEY_PARTS = (
23
+ "secret",
24
+ "token",
25
+ "password",
26
+ "passwd",
27
+ "credential",
28
+ "private",
29
+ "api_key",
30
+ "apikey",
31
+ "access_key",
32
+ "session_key",
33
+ "auth",
34
+ "bearer",
35
+ )
36
+
37
+
38
+ def omitted_line(count: int, label: str, noun: str = "row") -> str:
39
+ plural = "" if count == 1 else "s"
40
+ return f"… {count} {label} {noun}{plural} omitted"
41
+
42
+
43
+ def is_sensitive_key(key: str) -> bool:
44
+ lower = key.lower().replace("-", "_")
45
+ return (
46
+ lower == "key"
47
+ or lower.endswith("_key")
48
+ or any(part in lower for part in _SENSITIVE_KEY_PARTS)
49
+ )
50
+
51
+
52
+ def is_opaque_value(value: str) -> bool:
53
+ if len(value) < 64:
54
+ return False
55
+ compact = value.strip()
56
+ if "://" in compact:
57
+ return True
58
+ allowed = sum(
59
+ 1 for char in compact if char.isalnum() or char in "+/=_-.:"
60
+ )
61
+ return allowed * 100 >= len(compact) * 95 and not any(
62
+ char.isspace() for char in compact
63
+ )
64
+
65
+
66
+ def format_env_var(key: str, value: str) -> str:
67
+ if key == "PATH":
68
+ return f"{key}={_format_path_value(value)}"
69
+ if is_sensitive_key(key) or is_opaque_value(value):
70
+ return f"{key}=[redacted]"
71
+ return f"{key}={value}"
72
+
73
+
74
+ def redact_sensitive_text(text: str) -> str:
75
+ """Redact obvious secret-bearing argv/env tokens without exposing values."""
76
+
77
+ tokens = text.split()
78
+ if not tokens:
79
+ return text
80
+
81
+ output: list[str] = []
82
+ redact_next = False
83
+ changed = False
84
+ for token in tokens:
85
+ if redact_next:
86
+ output.append("[redacted]")
87
+ redact_next = False
88
+ changed = True
89
+ continue
90
+
91
+ assignment = _sensitive_assignment_prefix(token)
92
+ if assignment is not None:
93
+ key_prefix, value = assignment
94
+ output.append(f"{key_prefix}=[redacted]")
95
+ if value.lower() in {"bearer", "basic"}:
96
+ redact_next = True
97
+ changed = True
98
+ continue
99
+
100
+ if token.lower() == "bearer":
101
+ output.append(token)
102
+ redact_next = True
103
+ continue
104
+
105
+ if token.startswith("--"):
106
+ if "=" in token:
107
+ option, _value = token.split("=", 1)
108
+ if is_sensitive_key(option.lstrip("-")):
109
+ output.append(f"{option}=[redacted]")
110
+ changed = True
111
+ continue
112
+ elif is_sensitive_key(token.lstrip("-")):
113
+ output.append(token)
114
+ redact_next = True
115
+ continue
116
+
117
+ output.append(token)
118
+
119
+ if not changed:
120
+ return text
121
+ return " ".join(output)
122
+
123
+
124
+ def _format_path_value(value: str) -> str:
125
+ separator = (
126
+ ";"
127
+ if ";" in value and re.search(r"(?:^|;)[A-Za-z]:[\\/]", value)
128
+ else ";" if value.count(";") > value.count(":") else ":"
129
+ )
130
+ entries = [entry for entry in value.split(separator) if entry]
131
+ if not entries:
132
+ return "[]"
133
+ if len(entries) == 1:
134
+ return entries[0]
135
+ return (
136
+ f"[path entries={len(entries)} first={entries[0]} last={entries[-1]}]"
137
+ )
138
+
139
+
140
+ def _sensitive_assignment_prefix(token: str) -> tuple[str, str] | None:
141
+ if "=" not in token:
142
+ return None
143
+ parts = token.split("=")
144
+ for index in range(len(parts) - 1):
145
+ key = parts[index].rsplit(",", 1)[-1]
146
+ if key.startswith("--"):
147
+ key = key.lstrip("-")
148
+ value = "=".join(parts[index + 1 :])
149
+ if not value:
150
+ continue
151
+ if _is_assignment_key(key) and (
152
+ is_sensitive_key(key) or _is_sensitive_url_assignment(key, value)
153
+ ):
154
+ prefix = "=".join(parts[: index + 1])
155
+ return prefix, value
156
+ return None
157
+
158
+
159
+ def _is_assignment_key(key: str) -> bool:
160
+ return bool(key) and key.replace("_", "").replace("-", "").isalnum()
161
+
162
+
163
+ def _is_sensitive_url_assignment(key: str, value: str) -> bool:
164
+ return key.lower().endswith("_url") and "://" in value
@@ -0,0 +1,7 @@
1
+ """Structured table shellion."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .reducer import compress_table_output
6
+
7
+ __all__ = ["compress_table_output"]