cobalt-cli-linux 0.1.1__tar.gz → 0.1.3__tar.gz
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.
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/README.md +2 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/pyproject.toml +1 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/tui.py +89 -42
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/PKG-INFO +3 -1
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/tests/test_cli.py +10 -2
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/setup.cfg +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/ASCII.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/__init__.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/__main__.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/agent.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/cli.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/config.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/deepseek_client.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/executor.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/groq_client.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux/history.py +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/SOURCES.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/dependency_links.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/entry_points.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/requires.txt +0 -0
- {cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobalt-cli-linux
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,6 +77,8 @@ cobalt --interactive
|
|
|
77
77
|
|
|
78
78
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
79
79
|
|
|
80
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
81
|
+
|
|
80
82
|
You can also pass arguments directly:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -52,6 +52,8 @@ cobalt --interactive
|
|
|
52
52
|
|
|
53
53
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
54
54
|
|
|
55
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
56
|
+
|
|
55
57
|
You can also pass arguments directly:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
@@ -8,6 +8,14 @@ from pathlib import Path
|
|
|
8
8
|
from .agent import CobaltAgent
|
|
9
9
|
from .history import ChatStore
|
|
10
10
|
|
|
11
|
+
COBALT_CLI_ART = (
|
|
12
|
+
" ____ ___ ____ _ _ _____ ____ _ ___ ",
|
|
13
|
+
" / ___/ _ \\| __ ) / \\ | | |_ _| / ___| | |_ _|",
|
|
14
|
+
" | | | | | | _ \\ / _ \\ | | | | | | | | | | ",
|
|
15
|
+
" | |__| |_| | |_) / ___ \\| |___| | | |___| |___ | | ",
|
|
16
|
+
" \\____\\___/|____/_/ \\_\\_____|_| \\____|_____|___|",
|
|
17
|
+
)
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
class CobaltTUI:
|
|
13
21
|
def __init__(self, settings) -> None:
|
|
@@ -22,15 +30,7 @@ class CobaltTUI:
|
|
|
22
30
|
self.current_chat = self.chat_store.load_chat(self.selected_chat_path)
|
|
23
31
|
self.history = self.current_chat
|
|
24
32
|
self.saved_chats = self.chat_files
|
|
25
|
-
self.
|
|
26
|
-
|
|
27
|
-
def _load_ascii_art(self) -> list[str]:
|
|
28
|
-
ascii_path = Path(__file__).with_name("ASCII.txt")
|
|
29
|
-
if not ascii_path.exists():
|
|
30
|
-
ascii_path = Path(__file__).resolve().parents[2] / "ASCII.txt"
|
|
31
|
-
if not ascii_path.exists():
|
|
32
|
-
return ["COBALT"]
|
|
33
|
-
return [line.rstrip() for line in ascii_path.read_text(encoding="utf-8").splitlines() if line.strip()]
|
|
33
|
+
self.input_focused = True
|
|
34
34
|
|
|
35
35
|
def _refresh_chat_files(self) -> None:
|
|
36
36
|
self.chat_files = self.chat_store.list_chats()
|
|
@@ -58,8 +58,38 @@ class CobaltTUI:
|
|
|
58
58
|
title = " ".join(words[:4]).strip()
|
|
59
59
|
return title.title() if title else "New chat"
|
|
60
60
|
|
|
61
|
-
def _should_handle_shortcut(self, ch: int) -> bool:
|
|
62
|
-
return ch in (14, 4)
|
|
61
|
+
def _should_handle_shortcut(self, ch: int, input_focused: bool = False) -> bool:
|
|
62
|
+
return not input_focused and ch in (14, 4, ord("n"), ord("d"))
|
|
63
|
+
|
|
64
|
+
def _input_box_geometry(self, main_x: int, main_y: int, main_w: int, main_h: int, input_text: str) -> tuple[int, int, int, int, list[str]]:
|
|
65
|
+
box_width = max(20, main_w - 4)
|
|
66
|
+
text_width = max(10, box_width - 6)
|
|
67
|
+
input_lines = textwrap.wrap(
|
|
68
|
+
input_text,
|
|
69
|
+
width=text_width,
|
|
70
|
+
replace_whitespace=False,
|
|
71
|
+
drop_whitespace=False,
|
|
72
|
+
break_long_words=True,
|
|
73
|
+
break_on_hyphens=False,
|
|
74
|
+
) or [""]
|
|
75
|
+
max_lines = max(1, min(6, main_h - 6))
|
|
76
|
+
input_lines = input_lines[-max_lines:]
|
|
77
|
+
box_height = len(input_lines) + 3
|
|
78
|
+
box_x = main_x + 2
|
|
79
|
+
box_y = main_y + main_h - box_height - 1
|
|
80
|
+
return box_x, box_y, box_width, box_height, input_lines
|
|
81
|
+
|
|
82
|
+
def _draw_input_box(self, stdscr, box_x: int, box_y: int, box_width: int, box_height: int, input_lines: list[str], focused: bool) -> None:
|
|
83
|
+
rule = "-" * max(0, box_width - 2)
|
|
84
|
+
top_label = " Ask AI "
|
|
85
|
+
top = f"-{top_label}{rule}"
|
|
86
|
+
bottom_label = " [Enter to submit] "
|
|
87
|
+
bottom = f"-{'-' * max(0, box_width - len(bottom_label) - 2)}{bottom_label}-"
|
|
88
|
+
stdscr.addstr(box_y, box_x, top[:box_width])
|
|
89
|
+
for index, line in enumerate(input_lines):
|
|
90
|
+
prompt = "> " if index == 0 else " "
|
|
91
|
+
stdscr.addstr(box_y + 1 + index, box_x, f"{prompt}{line}"[:box_width])
|
|
92
|
+
stdscr.addstr(box_y + box_height - 1, box_x, bottom[:box_width], curses.A_BOLD if focused else curses.A_DIM)
|
|
63
93
|
|
|
64
94
|
def _compose_lines(self, text: str, width: int) -> list[str]:
|
|
65
95
|
lines: list[str] = []
|
|
@@ -93,8 +123,12 @@ class CobaltTUI:
|
|
|
93
123
|
|
|
94
124
|
sidebar_w = min(26, max(20, width // 4))
|
|
95
125
|
sidebar_x = 1
|
|
96
|
-
|
|
97
|
-
|
|
126
|
+
for index, line in enumerate(COBALT_CLI_ART):
|
|
127
|
+
x = max(0, (width - len(line)) // 2)
|
|
128
|
+
stdscr.addstr(index, x, line[: max(0, width - x)])
|
|
129
|
+
|
|
130
|
+
sidebar_y = 6
|
|
131
|
+
sidebar_h = max(8, height - 9)
|
|
98
132
|
self._draw_box(stdscr, sidebar_y, sidebar_x, sidebar_h, sidebar_w, " Chats ")
|
|
99
133
|
|
|
100
134
|
chat_files = self.chat_store.list_chats()[-8:]
|
|
@@ -112,20 +146,12 @@ class CobaltTUI:
|
|
|
112
146
|
|
|
113
147
|
main_x = sidebar_x + sidebar_w + 2
|
|
114
148
|
main_w = max(20, width - main_x - 2)
|
|
115
|
-
main_y =
|
|
116
|
-
main_h = max(8, height -
|
|
149
|
+
main_y = 6
|
|
150
|
+
main_h = max(8, height - 9)
|
|
117
151
|
self._draw_box(stdscr, main_y, main_x, main_h, main_w, f" {self.history.title or 'conversation'} ")
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
y = main_y + 2 + idx
|
|
122
|
-
x = main_x + max(0, (main_w - len(line)) // 2)
|
|
123
|
-
stdscr.addstr(y, x, line[: max(0, main_w - 2)])
|
|
124
|
-
prompt = f"> {input_text}" if input_text else "> "
|
|
125
|
-
prompt_y = main_y + 2 + len(self.ascii_lines) + 2
|
|
126
|
-
prompt_x = main_x + max(0, (main_w - len(prompt)) // 2)
|
|
127
|
-
stdscr.addstr(prompt_y, prompt_x, prompt[: max(0, main_w - 2)], curses.A_REVERSE)
|
|
128
|
-
else:
|
|
153
|
+
box_x, box_y, box_width, box_height, input_lines = self._input_box_geometry(main_x, main_y, main_w, main_h, input_text)
|
|
154
|
+
if self.history.messages:
|
|
129
155
|
rows: list[str] = []
|
|
130
156
|
for message in self.history.latest(18):
|
|
131
157
|
role = message.get("role", "user").upper()
|
|
@@ -135,16 +161,15 @@ class CobaltTUI:
|
|
|
135
161
|
rows.append(f"{prefix}{line}" if prefix and line else line)
|
|
136
162
|
prefix = ""
|
|
137
163
|
|
|
138
|
-
visible = rows[-max(
|
|
164
|
+
visible = rows[-max(1, box_y - main_y - 2):]
|
|
139
165
|
for idx, line in enumerate(visible[: main_h - 4]):
|
|
140
166
|
y = main_y + 1 + idx
|
|
167
|
+
if y >= box_y:
|
|
168
|
+
break
|
|
141
169
|
clipped = line[: max(0, main_w - 4)]
|
|
142
170
|
stdscr.addstr(y, main_x + 2, clipped)
|
|
143
171
|
|
|
144
|
-
|
|
145
|
-
prompt_y = main_y + main_h - 3
|
|
146
|
-
prompt_x = main_x + max(0, (main_w - len(prompt)) // 2)
|
|
147
|
-
stdscr.addstr(prompt_y, prompt_x, prompt[: max(0, main_w - 2)], curses.A_REVERSE)
|
|
172
|
+
self._draw_input_box(stdscr, box_x, box_y, box_width, box_height, input_lines, self.input_focused)
|
|
148
173
|
|
|
149
174
|
if status:
|
|
150
175
|
status_line = status[: max(0, width - 1)]
|
|
@@ -165,17 +190,22 @@ class CobaltTUI:
|
|
|
165
190
|
|
|
166
191
|
agent = CobaltAgent(settings=self.settings)
|
|
167
192
|
agent.history = self.history
|
|
168
|
-
agent.process_stream(input_text.strip(), on_chunk=on_chunk)
|
|
193
|
+
response = agent.process_stream(input_text.strip(), on_chunk=on_chunk)
|
|
169
194
|
self.history = agent.history
|
|
170
195
|
self.current_chat = self.history
|
|
171
196
|
self.saved_chats = self.chat_store.list_chats()
|
|
197
|
+
if response and not buffer:
|
|
198
|
+
buffer = response
|
|
199
|
+
if not buffer:
|
|
200
|
+
raise RuntimeError("The assistant returned an empty response")
|
|
201
|
+
self._render(stdscr, "", "")
|
|
172
202
|
return buffer
|
|
173
203
|
|
|
174
204
|
def _handle_mouse_click(self, x: int, y: int) -> bool:
|
|
175
205
|
sidebar_w = min(26, max(20, curses.COLS // 4))
|
|
176
206
|
sidebar_x = 1
|
|
177
|
-
sidebar_y =
|
|
178
|
-
sidebar_h = max(8, curses.LINES -
|
|
207
|
+
sidebar_y = 6
|
|
208
|
+
sidebar_h = max(8, curses.LINES - 9)
|
|
179
209
|
|
|
180
210
|
if sidebar_x <= x < sidebar_x + sidebar_w and sidebar_y <= y < sidebar_y + sidebar_h:
|
|
181
211
|
chat_files = self.chat_store.list_chats()[-8:]
|
|
@@ -190,6 +220,16 @@ class CobaltTUI:
|
|
|
190
220
|
return True
|
|
191
221
|
return False
|
|
192
222
|
|
|
223
|
+
def _click_is_in_input(self, x: int, y: int, input_text: str) -> bool:
|
|
224
|
+
height, width = curses.LINES, curses.COLS
|
|
225
|
+
sidebar_w = min(26, max(20, width // 4))
|
|
226
|
+
main_x = 3 + sidebar_w
|
|
227
|
+
main_y = 6
|
|
228
|
+
main_w = max(20, width - main_x - 2)
|
|
229
|
+
main_h = max(8, height - 9)
|
|
230
|
+
box_x, box_y, box_width, box_height, _ = self._input_box_geometry(main_x, main_y, main_w, main_h, input_text)
|
|
231
|
+
return box_x <= x < box_x + box_width and box_y <= y < box_y + box_height
|
|
232
|
+
|
|
193
233
|
def run(self) -> int:
|
|
194
234
|
def _main(stdscr):
|
|
195
235
|
curses.curs_set(0)
|
|
@@ -202,6 +242,7 @@ class CobaltTUI:
|
|
|
202
242
|
curses.noecho()
|
|
203
243
|
curses.mousemask(curses.BUTTON1_PRESSED | curses.BUTTON1_RELEASED | curses.BUTTON1_CLICKED)
|
|
204
244
|
input_text = ""
|
|
245
|
+
self.input_focused = True
|
|
205
246
|
status = ""
|
|
206
247
|
|
|
207
248
|
while True:
|
|
@@ -210,25 +251,32 @@ class CobaltTUI:
|
|
|
210
251
|
|
|
211
252
|
if ch == curses.KEY_MOUSE:
|
|
212
253
|
_, x, y, _, button_state = curses.getmouse()
|
|
254
|
+
if button_state & (curses.BUTTON1_PRESSED | curses.BUTTON1_CLICKED | curses.BUTTON1_RELEASED) and self._click_is_in_input(x, y, input_text):
|
|
255
|
+
self.input_focused = True
|
|
256
|
+
continue
|
|
213
257
|
if button_state & (curses.BUTTON1_PRESSED | curses.BUTTON1_CLICKED | curses.BUTTON1_RELEASED) and self._handle_mouse_click(x, y):
|
|
214
|
-
status = "
|
|
258
|
+
status = ""
|
|
215
259
|
input_text = ""
|
|
260
|
+
self.input_focused = False
|
|
216
261
|
continue
|
|
217
262
|
|
|
218
263
|
if ch == 3:
|
|
219
264
|
break
|
|
220
|
-
if
|
|
221
|
-
|
|
265
|
+
if ch == 27:
|
|
266
|
+
self.input_focused = False
|
|
267
|
+
continue
|
|
268
|
+
if self._should_handle_shortcut(ch, self.input_focused):
|
|
269
|
+
if ch in (14, ord("n")):
|
|
222
270
|
new_chat = self.chat_store.new_chat(title="New chat")
|
|
223
271
|
self.current_chat = new_chat
|
|
224
272
|
self.history = self.current_chat
|
|
225
273
|
self.selected_chat_path = str(self.current_chat.path)
|
|
226
274
|
self.chat_files = self.chat_store.list_chats()
|
|
227
275
|
input_text = ""
|
|
228
|
-
status = "
|
|
276
|
+
status = ""
|
|
229
277
|
continue
|
|
230
278
|
|
|
231
|
-
if ch
|
|
279
|
+
if ch in (4, ord("d")) and self.selected_chat_path:
|
|
232
280
|
target = Path(self.selected_chat_path)
|
|
233
281
|
if target.exists():
|
|
234
282
|
self.chat_store.delete_chat(target)
|
|
@@ -242,7 +290,7 @@ class CobaltTUI:
|
|
|
242
290
|
self.history = self.current_chat
|
|
243
291
|
self.selected_chat_path = str(self.current_chat.path)
|
|
244
292
|
input_text = ""
|
|
245
|
-
status = "
|
|
293
|
+
status = ""
|
|
246
294
|
continue
|
|
247
295
|
if ch in (10, 13, curses.KEY_ENTER):
|
|
248
296
|
if input_text.strip():
|
|
@@ -251,7 +299,7 @@ class CobaltTUI:
|
|
|
251
299
|
self.history.title = self._suggest_chat_title(input_text.strip())
|
|
252
300
|
self.history.save()
|
|
253
301
|
self._stream_response(stdscr, input_text)
|
|
254
|
-
status = "
|
|
302
|
+
status = ""
|
|
255
303
|
except (OSError, RuntimeError, ValueError) as exc:
|
|
256
304
|
self.history.add("assistant", f"Error: {exc}")
|
|
257
305
|
status = ""
|
|
@@ -263,9 +311,8 @@ class CobaltTUI:
|
|
|
263
311
|
if ch in (curses.KEY_RESIZE,):
|
|
264
312
|
continue
|
|
265
313
|
if 32 <= ch <= 126:
|
|
314
|
+
self.input_focused = True
|
|
266
315
|
input_text += chr(ch)
|
|
267
|
-
elif ch == 27:
|
|
268
|
-
break
|
|
269
316
|
|
|
270
317
|
return 0
|
|
271
318
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cobalt-cli-linux
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Agentic Groq-powered CLI assistant for Debian Linux
|
|
5
5
|
Author: Cobalt
|
|
6
6
|
License-Expression: MIT
|
|
@@ -77,6 +77,8 @@ cobalt --interactive
|
|
|
77
77
|
|
|
78
78
|
This launches a fullscreen TUI that feels similar to a professional agent console, with the message stream and a compact prompt area instead of a raw shell prompt.
|
|
79
79
|
|
|
80
|
+
In the interface, press `Escape` to unfocus the prompt. While unfocused, `n` starts a new chat and `d` deletes the selected chat; click the prompt or type a character to focus it again. Long prompts wrap inside the prompt box.
|
|
81
|
+
|
|
80
82
|
You can also pass arguments directly:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
@@ -51,9 +51,11 @@ def test_tui_shortcuts_ignore_typed_letters() -> None:
|
|
|
51
51
|
|
|
52
52
|
assert tui._should_handle_shortcut(14) is True
|
|
53
53
|
assert tui._should_handle_shortcut(4) is True
|
|
54
|
+
assert tui._should_handle_shortcut(ord("n")) is True
|
|
55
|
+
assert tui._should_handle_shortcut(ord("d")) is True
|
|
54
56
|
assert tui._should_handle_shortcut(ord("q")) is False
|
|
55
|
-
assert tui._should_handle_shortcut(ord("n")) is False
|
|
56
|
-
assert tui._should_handle_shortcut(ord("d")) is False
|
|
57
|
+
assert tui._should_handle_shortcut(ord("n"), True) is False
|
|
58
|
+
assert tui._should_handle_shortcut(ord("d"), True) is False
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def test_q_is_not_a_shortcut_when_typing() -> None:
|
|
@@ -124,6 +126,12 @@ def test_stream_error_reads_response_before_inspecting_body(monkeypatch) -> None
|
|
|
124
126
|
raise AssertionError("Expected streamed API error")
|
|
125
127
|
|
|
126
128
|
|
|
129
|
+
def test_tui_empty_chat_does_not_load_prompt_art_as_body_content(tmp_path: Path) -> None:
|
|
130
|
+
tui = CobaltTUI(settings=Settings())
|
|
131
|
+
|
|
132
|
+
assert not hasattr(tui, "ascii_lines")
|
|
133
|
+
|
|
134
|
+
|
|
127
135
|
def test_write_file_tool_creates_and_verifies_file(tmp_path: Path) -> None:
|
|
128
136
|
agent = CobaltAgent(settings=Settings(), history=ConversationHistory(tmp_path / "history.json"))
|
|
129
137
|
target = tmp_path / "test.txt"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/requires.txt
RENAMED
|
File without changes
|
{cobalt_cli_linux-0.1.1 → cobalt_cli_linux-0.1.3}/src/cobalt_cli_linux.egg-info/top_level.txt
RENAMED
|
File without changes
|