Skip to content

feat: Add Uplink Terminal GUI and watcher script#54

Open
chromaglow wants to merge 4 commits intoverygoodplugins:mainfrom
chromaglow:feature/uplink-terminal
Open

feat: Add Uplink Terminal GUI and watcher script#54
chromaglow wants to merge 4 commits intoverygoodplugins:mainfrom
chromaglow:feature/uplink-terminal

Conversation

@chromaglow
Copy link

Summary

This PR adds the Automem Uplink Terminal, a desktop integration tool that allows for automatic memory ingestion via a "Drop Zone" folder.

Features

  • Folder Watcher (scripts/folder_watch.py): Monitors ~/Desktop/AutomemDropZone (configurable) and automatically ingests new .txt files into the Automem API.
  • Desktop GUI (scripts/watcher_gui.py): A Python/Tkinter control panel with a "90s Hacker/DOS" aesthetic (Black/Green theme).
    • Start/Stop controls for the watcher service.
    • Real-time "Data Stream" logs.
    • Connectivity health checks for the Automem API.

Motivation

Simplifies the ingestion workflow by allowing users to simply drag-and-drop text files to add memories, rather than using CLI commands or API calls manually.

Usage

  1. Run python scripts/watcher_gui.py
  2. Click [ INITIATE SEQUENCE ]
  3. Drop text files into the AutomemDropZone folder on the Desktop.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Introduced Automem Uplink Terminal GUI—a hacker-themed interface for monitoring a designated folder and automatically ingesting text files into the Automem API.
    • Added real-time status indicators and health monitoring for the API connection.
    • Configuration via environment variables for drop zone location, API endpoint, and authentication.
  • Documentation

    • Added comprehensive setup and usage guide for the Uplink Terminal, including installation requirements and troubleshooting steps.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds an Automem Uplink Terminal: a README plus two new Python tools. A folder watcher ingests .txt files from a configurable drop zone to the Automem API and archives processed files. A Tkinter "HackerTerminal" GUI controls the watcher, shows live logs, and polls backend health.

Changes

Cohort / File(s) Summary
Documentation
UPLINK_README.md
New README describing the Automem Uplink Terminal: features, install, usage, env config (AUTOMEM_DROP_ZONE, AUTOMEM_API_URL, AUTOMEM_API_TOKEN), and troubleshooting.
Folder Monitoring
scripts/folder_watch.py
New watcher that resolves desktop/drop-zone path, scans existing .txt files, and monitors created/moved .txt files. Reads file content, POSTs JSON to {BASE_URL}/memory with Bearer TOKEN (from env), moves successful files to a processed subdir with collision-handling, and logs to stdout. Exposes process_file_util and MemoryHandler.
User Interface
scripts/watcher_gui.py
New Tkinter "HackerTerminal" GUI with monospaced theme, stdout/stderr redirection into a scrollable console, start/stop controls for the watchdog Observer, spinner animation, background health polling, and graceful shutdown. Exposes TextRedirector, HackerTerminal, and UI constants.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant GUI as HackerTerminal
    participant FS as FileSystem
    participant Watch as MemoryHandler
    participant API as Automem API

    User->>GUI: Click "INITIATE SEQUENCE"
    GUI->>Watch: Start watchdog Observer
    GUI->>FS: Ensure processed/ subdirectory exists
    GUI->>GUI: Update status/UI

    User->>FS: Drop file.txt into AutomemDropZone
    FS->>Watch: on_created / on_moved event
    Watch->>Watch: Read and prepare payload
    Watch->>API: POST /memory (Bearer token)

    alt 200/201 Success
        API-->>Watch: Success
        Watch->>FS: Move file to processed/ (rename on collision)
        Watch->>GUI: Log success
        GUI-->>User: Show success in console
    else Failure
        API-->>Watch: Error
        Watch->>GUI: Log error
        GUI-->>User: Show error in console
    end

    User->>GUI: Click "TERMINATE"
    GUI->>Watch: Stop Observer and join
    GUI->>GUI: Update status/UI to stopped
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding an Uplink Terminal GUI and folder watcher script, which are the primary features in this changeset.
Description check ✅ Passed The description is clearly related to the changeset, providing context on the new Uplink Terminal feature, its components, motivation, and usage instructions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@scripts/folder_watch.py`:
- Around line 75-92: The requests.post call that sends payload to
f"{BASE_URL}/memory" needs an explicit timeout to avoid hanging; add a timeout
parameter (e.g., timeout=10 or a constant like TIMEOUT_SECONDS) to the
requests.post invocation and update the exception handling to catch
requests.exceptions.Timeout (and/or requests.exceptions.RequestException) so
timeouts produce a clear error message instead of hanging; refer to the existing
resp = requests.post(f"{BASE_URL}/memory", json=payload, headers=HEADERS) line
and the except requests.exceptions.ConnectionError block to make these changes
and log a distinct message for timeouts.
- Around line 29-42: Remove the internal rambling comments and replace them with
a concise note that TOKEN is read from AUTOMEM_API_TOKEN and intentionally
defaults to empty (no hardcoded token). Keep BASE_URL and HEADERS as-is, but
change the startup behavior so when TOKEN is empty the module emits a clear
warning (use the module logger via logging.getLogger(__name__).warning or
similar) informing the user that no API token is set and that calls using
HEADERS will likely fail; do not default to any real token or silently proceed.
Ensure references to TOKEN and HEADERS remain intact so the warning reflects the
actual header construction.
- Around line 105-109: The on_moved method has inconsistent indentation (lines
within def on_moved use 3 spaces instead of 4) which will trigger
style/formatting errors; fix by normalizing the block to standard 4-space
indentation for the if, print, time.sleep, and process_file_util lines inside
def on_moved so the body aligns properly under def on_moved and maintains
consistent spacing around the event checks (event.is_directory,
event.dest_path.endswith, "processed" not in event.dest_path) and the call to
process_file_util(event.dest_path).

In `@scripts/watcher_gui.py`:
- Around line 124-140: The nested _check() function inside check_health uses
recursion (calling _check() after sleep) which will grow the call stack
indefinitely and also catches all exceptions with a bare except; replace the
recursive design with a while loop that runs while not self.root_destroyed,
performing the same requests.get to f"{BASE_URL}/health", updating GUI via
self.update_gui(lambda: self.brain_value.configure(...)) and sleeping 10s
between iterations, and change the bare except to catch specific exceptions
(e.g., requests.RequestException and optionally Exception for unexpected errors)
so you don't silence errors; keep launching the loop in a daemon thread as
before (threading.Thread(target=<loop_runner>, daemon=True).start()).
- Around line 19-35: The write method in class TextRedirector shadows the
built-in str and uses a bare except; rename the parameter from str to text
(update usages in TextRedirector.write) and replace the bare except with an
explicit except tk.TclError: pass (and ensure tkinter is imported as tk at top
if not already) so widget-destruction errors are handled without silencing other
problems.

In `@UPLINK_README.md`:
- Line 5: Replace the placeholder image Markdown element (the line containing
"![Uplink
Terminal](https://via.placeholder.com/600x400?text=Uplink+Terminal+Preview)")
with a real screenshot of the Uplink Terminal (hosted in the repo or a stable
CDN) or remove the image tag entirely; ensure the alt text remains descriptive
(e.g., "Uplink Terminal") and update the URL to point to the committed asset or
final production location.
🧹 Nitpick comments (7)
scripts/folder_watch.py (4)

1-10: Missing type hints throughout the module.

As per coding guidelines, Python files must use type hints. The functions and methods in this module lack type annotations.

Example type hints for key functions
-def process_file_util(file_path):
+def process_file_util(file_path: str) -> None:

For the class methods:

-def on_created(self, event):
+def on_created(self, event: FileSystemEvent) -> None:

-def on_moved(self, event):
+def on_moved(self, event: FileSystemEvent) -> None:

64-67: Shadowing module-level HEADERS constant inside function.

The local HEADERS variable shadows the module-level constant defined at lines 39-42. This is redundant and confusing. Either use the module-level constant or rename the local variable.

Proposed fix
-        HEADERS = {
-            "Authorization": f"Bearer {TOKEN}",
-            "Content-Type": "application/json"
-        }
-        
         payload = {
             "content": content,
             "type": "Context",
             "metadata": {"source": "drop_zone", "filename": os.path.basename(file_path)}
         }

Since HEADERS is already defined at module level with the same values, remove the local redefinition.


94-95: Avoid catching bare Exception; catch specific exceptions.

Catching Exception broadly can mask unexpected errors and make debugging difficult (BLE001). Consider catching specific exceptions like IOError, OSError, or UnicodeDecodeError that are likely during file processing.

Proposed fix
-    except Exception as e:
-        print(f"Error processing file: {e}")
+    except (IOError, OSError, UnicodeDecodeError) as e:
+        print(f"Error processing file: {e}")

117-122: Potential iteration over directory without checking for files vs. subdirectories.

The startup scan at lines 119-122 iterates over DROP_ZONE but doesn't explicitly check os.path.isfile(). While .endswith(".txt") likely filters out directories, it's defensive to add an explicit file check, especially since PROCESSED_DIR is a subdirectory.

Proposed fix
     if os.path.exists(DROP_ZONE):
         for filename in os.listdir(DROP_ZONE):
-            if filename.endswith(".txt"):
+            if filename.endswith(".txt") and os.path.isfile(os.path.join(DROP_ZONE, filename)):
                 filepath = os.path.join(DROP_ZONE, filename)
                 process_file_util(filepath)
scripts/watcher_gui.py (2)

182-188: Bare except in root_destroyed property; prefer specific exception.

The bare except: at line 187 catches all exceptions. For checking widget existence, catch tk.TclError specifically.

Proposed fix
     `@property`
-    def root_destroyed(self):
+    def root_destroyed(self) -> bool:
         try:
-            self.root.winfo_exists()
-            return False
-        except:
+            return not self.root.winfo_exists()
+        except tk.TclError:
             return True

37-113: Missing type hints on HackerTerminal class methods.

As per coding guidelines, type hints are required. Key methods like __init__, animate, start_watcher, stop_watcher, etc. lack type annotations.

Example type hints
def __init__(self, root: tk.Tk) -> None:
def animate(self) -> None:
def check_health(self) -> None:
def update_gui(self, func: Callable[[], None]) -> None:
def start_watcher(self) -> None:
def stop_watcher(self) -> None:

Add from typing import Callable to imports.

UPLINK_README.md (1)

16-21: Consider noting that tkinter may require separate installation on some Linux distributions.

While the note mentions tkinter is included with standard Python, on Debian/Ubuntu-based systems it often requires sudo apt install python3-tk. This could save users troubleshooting time.

Suggested addition
     pip install watchdog requests
     ```
-    *(Note: `tkinter` is included with standard Python installations)*
+    *(Note: `tkinter` is included with standard Python installations on Windows/macOS. On Debian/Ubuntu, you may need: `sudo apt install python3-tk`)*

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@scripts/watcher_gui.py`:
- Around line 137-143: The lambda passed to update_gui closes over resp and may
see a different resp when executed later; capture resp.status_code eagerly by
binding it as a default argument in the lambda (e.g., lambda
status=resp.status_code: self.brain_value.configure(...)) so update_gui uses the
captured status, and do the same for the success branch (capture text/fg or
status) when calling self.update_gui and self.brain_value.configure.
- Around line 194-201: The root_destroyed property currently ignores the return
value of self.root.winfo_exists(); change it to read and use that result (call
exists = self.root.winfo_exists()) and return True when exists is 0 (or False
when exists is 1) instead of always returning False; update the property
(root_destroyed) to return the boolean equivalent of the winfo_exists() result
(e.g., return not bool(exists) or return exists == 0) while still catching
tk.TclError and returning True on exception.
- Around line 151-155: Add type hints across the module: import Callable from
typing, annotate the TextRedirector class methods (e.g., its __init__, write,
flush) and all public methods of HackerTerminal (including update_gui(self,
func), on_closing callback, and any other public methods) with appropriate
parameter and return types (e.g., func: Callable[[], None] or Callable[...,
None], self -> None for constructors, write -> None, flush -> None). Ensure
update_gui signature becomes update_gui(self, func: Callable[[], None]) -> None
and on_closing is typed (e.g., on_closing(self) -> None or on_closing(self,
event: Optional[Any]) -> None as appropriate), and add any necessary imports
(from typing import Callable, Optional, Any) to satisfy project standards.
- Around line 25-33: TextRedirector.write currently mutates the Tk widget
directly from background threads (e.g., triggered by MemoryHandler.on_created
prints), which is unsafe; change TextRedirector to enqueue incoming text into a
thread-safe queue.Queue and add a drain method scheduled on the GUI thread via
widget.after (or widget.after_idle) that dequeues and performs the
widget.configure/insert/see/update_idletasks operations; update any callers
(including print hooks or where TextRedirector is attached) to keep using
write() but rely on the internal queue so all actual widget access happens in
the main Tk thread.

@chromaglow chromaglow force-pushed the feature/uplink-terminal branch from f91eb1c to d4ff405 Compare January 23, 2026 14:40
@jack-arturo
Copy link
Member

@codex - These errors in the pre-commit look like it's just maybe punctuation and spacing from Windows to Linux. Could you take a look at that and if that's the case, could you fix that for us here in the cloud?

@chatgpt-codex-connector
Copy link

To use Codex here, create an environment for this repo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants