Skip to content

Commit 5cca0ae

Browse files
feat(relay): complete v1.0 production hardening (Pass 2–4)
- Enforce maxBlobSize (10MB + RL003) on both upload handlers - Apply rate limiting to all mutating endpoints (including handlePeers POST) - Convert every http.Error to consistent JSON error responses (RL00x codes) - Add inbox entry cap (50 per receiver) - Wire live relay status through /version - Replace TUI placeholder with real "Relay (v1.0)" status box - Add 3 new doctor checks (connectivity, hasPeers, auto-upload consistency) → 5 total Closes the major gaps left from the previous incomplete run per docs/RELAY_V1_PRODUCTION_COMPLETION_PLAN.md. Follow-up cleanup pass will handle final verification polish and any remaining noise.
1 parent 1ff1aeb commit 5cca0ae

19 files changed

Lines changed: 864 additions & 83 deletions

File tree

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ gosec ./...
8181
- `cmd/pastelocal/` — Main CLI
8282
- `cmd/pastelocald/` — The daemon
8383
- `cmd/pastelocal-remote/` — Remote helper (small binary installed on remote hosts)
84-
- `cmd/relay-server/`Experimental multi-device relay server
84+
- `cmd/relay-server/`Relay server (v1.0 multi-device support)
8585
- `internal/server/` — Core daemon logic (handlers, history, snippets, redaction, etc.)
8686
- `internal/clipboard/` — Cross-platform clipboard access
8787
- `internal/crypto/` — X25519 + AES-GCM primitives (used by relay)
@@ -90,7 +90,7 @@ gosec ./...
9090

9191
## Notes on the Relay Feature
9292

93-
The multi-device relay (`pastelocal relay ...` and the standalone relay server) is currently **experimental**. Contributions that improve stability, add persistence, or complete the send path are very welcome, but please coordinate first by opening an issue.
93+
The multi-device relay (`pastelocal relay ...` and the standalone relay server) is a stable v1.0 feature. Contributions that improve it (e.g. more robust rate limiting, additional transports) are very welcome — open an issue or PR.
9494

9595
## Questions?
9696

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,13 @@ make build
5959

6060
### Integration with Agentic Coding Tools
6161
- `/paste` — Paste current clipboard
62-
- `/paste-history` — Choose from recent clipboard entries
62+
- `/paste-history` — Choose from recent clipboard entries (or use the new `/recall` skill for natural-language semantic search)
6363
- `/paste-snippet` — Recall saved named snippets
6464
- `/paste-send` — Send files from the remote machine back to your local clipboard
65+
- `/recall` — Natural language search over history (Recall v1)
6566

6667
### Productivity Tools
67-
- **Clipboard History** — Go back in time with `pastelocal-remote --list`
68+
- **Clipboard History** — Go back in time with `pastelocal-remote --list` or the powerful `--search "natural language query"` (Recall v1)
6869
- **Named Snippets** — Save frequently used text or images locally
6970
- **TUI Dashboard** — Run `pastelocal` to see daemon status, hosts, and recent activity
7071
- **Doctor**`pastelocal doctor --fix` automatically diagnoses and repairs most issues
@@ -83,7 +84,19 @@ make build
8384
command = "tesseract - - 2>/dev/null || true"
8485
```
8586

86-
### Experimental
87+
- **Recall v1 (Natural Language History Search)**: Semantic search over clipboard history using user-supplied embedding commands (Ollama, local Python, etc.). Query with `pastelocal-remote --search "the docker error from last night"` or the `/recall` skill. Results are ranked by cosine similarity over text content (and VisionPaste OCR+description for screenshots — the killer combo).
88+
- Privacy: Concealed items are never embedded or returned. Embeddings live only in memory (lost on daemon restart in v1).
89+
- Enable with a 3-line config block + one of the ready-made scripts in `docs/examples/`.
90+
- Example (Ollama + nomic-embed-text, copy the script too):
91+
```toml
92+
[recall]
93+
enabled = true
94+
timeout_seconds = 30
95+
command = "python3 -u ~/.config/pastelocal/embed_ollama.py"
96+
```
97+
- Then from any agent: `pastelocal-remote --search "..." --limit 5` and fetch winners with `--id <id>`.
98+
99+
### Multi-Device Relay (v1.0)
87100
- **Multi-device Relay** — E2E encrypted clipboard sync without SSH tunnels (see RELAY.md for full details, including sensitive clipboard filtering for password managers).
88101

89102
---
@@ -150,6 +163,9 @@ This works reliably with most agentic coding tools.
150163
```bash
151164
pastelocal-remote --list
152165
pastelocal-remote --list --index 3
166+
# or semantic search (requires [recall] enabled):
167+
pastelocal-remote --search "the stack trace from the failed test"
168+
pastelocal-remote --id <id-from-search>
153169
```
154170

155171
### Named Snippets
@@ -209,7 +225,7 @@ size = 20
209225
ttl_seconds = 3600
210226

211227
[relay]
212-
enabled = false # Experimental
228+
enabled = false # v1.0 stable (opt-in)
213229
relay_url = "http://localhost:7332"
214230
auto_upload = false
215231
```

cmd/pastelocal/grok.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// canonicalPasteSkillHeader is used by hasGrokSkill to validate that a probed
14+
// source tree contains *our* canonical skill (defense against malicious trees
15+
// in probed locations like cwd or ~/src).
16+
var canonicalPasteSkillHeader = []byte("# PasteLocal /paste — Grok Skill")
17+
18+
// ── grok (agent integration) ─────────────────────────────────────────────────
19+
20+
var grokCmd = &cobra.Command{
21+
Use: "grok",
22+
Short: "Grok / agentic coding tool integration helpers",
23+
GroupID: "grok",
24+
}
25+
26+
var grokInstallCmd = &cobra.Command{
27+
Use: "install-skills",
28+
Short: "Install or update PasteLocal Grok skills (paste, history, snippet, send, recall) into ~/.grok/skills/",
29+
Args: cobra.NoArgs,
30+
RunE: runGrokInstallSkills,
31+
}
32+
33+
func init() {
34+
grokCmd.AddCommand(grokInstallCmd)
35+
rootCmd.AddCommand(grokCmd)
36+
}
37+
38+
func runGrokInstallSkills(cmd *cobra.Command, args []string) error {
39+
srcDir, err := findGrokSourceDir()
40+
if err != nil {
41+
return fail("%v", err)
42+
}
43+
44+
home, err := os.UserHomeDir()
45+
if err != nil {
46+
return fail("could not determine home directory: %v", err)
47+
}
48+
dstBase := filepath.Join(home, ".grok", "skills")
49+
// 0755 is intentional for a user-discoverable skills directory (Grok/AI tools must read SKILL.md).
50+
// Contrast with 0700/0600 used only for private tokens/keys (see SECURITY.md and auth/store).
51+
if err := os.MkdirAll(dstBase, 0755); err != nil {
52+
return fail("failed to create ~/.grok/skills: %v", err)
53+
}
54+
55+
skills := []string{"paste", "paste-send", "recall", "paste-history", "paste-snippet"}
56+
copied := 0
57+
for _, name := range skills {
58+
src := filepath.Join(srcDir, name)
59+
if _, err := os.Stat(filepath.Join(src, "SKILL.md")); err != nil {
60+
continue // skip missing (future-proof)
61+
}
62+
dst := filepath.Join(dstBase, "pastelocal-"+name)
63+
_ = os.RemoveAll(dst) // clean previous
64+
cpBin := "/bin/cp"
65+
if _, err := os.Stat(cpBin); err != nil {
66+
cpBin = "/usr/bin/cp"
67+
}
68+
cp := exec.Command(cpBin, "-r", src, dst)
69+
if output, err := cp.CombinedOutput(); err != nil {
70+
return fail("failed to install skill %s: %v\n%s", name, err, string(output))
71+
}
72+
copied++
73+
}
74+
75+
if copied == 0 {
76+
return fail("no skills were copied from %s (run from PasteLocal source tree or provide correct layout)", srcDir)
77+
}
78+
fmt.Printf("PasteLocal Grok skills installed successfully (%d skills).\n", copied)
79+
fmt.Println(" Discoverable as: /paste, /paste-send, /paste-history, /paste-snippet, /recall")
80+
fmt.Println("\nUpdate anytime with: pastelocal grok install-skills (after git pull)")
81+
return nil
82+
}
83+
84+
// findGrokSourceDir locates the skill/grok tree relative to the running binary,
85+
// cwd, or common install locations. Prefers source tree clones.
86+
func findGrokSourceDir() (string, error) {
87+
// Try relative to the executable (works when running from a build next to the repo tree).
88+
if exe, err := os.Executable(); err == nil {
89+
dir := filepath.Dir(exe)
90+
for _, rel := range []string{"../skill/grok", "skill/grok", "../../skill/grok"} {
91+
cand := filepath.Join(dir, rel)
92+
if hasGrokSkill(cand) {
93+
return filepath.Clean(cand), nil
94+
}
95+
}
96+
}
97+
98+
// CWD or parent (user is inside the cloned repo).
99+
if cwd, err := os.Getwd(); err == nil {
100+
for _, rel := range []string{"skill/grok", "../skill/grok", "PasteLocal/skill/grok"} {
101+
cand := filepath.Join(cwd, rel)
102+
if hasGrokSkill(cand) {
103+
return filepath.Clean(cand), nil
104+
}
105+
}
106+
}
107+
108+
// Fallback locations for packaged or user installs.
109+
home, homeErr := os.UserHomeDir()
110+
for _, cand := range []string{
111+
filepath.Join(home, ".local", "share", "pastelocal", "skill", "grok"),
112+
filepath.Join(home, "src", "PasteLocal", "skill", "grok"),
113+
"/usr/local/share/pastelocal/skill/grok",
114+
} {
115+
if hasGrokSkill(cand) {
116+
return cand, nil
117+
}
118+
}
119+
120+
if homeErr != nil {
121+
return "", fmt.Errorf("PasteLocal grok skills source not found (home probe err: %v). Run from a clone of the official repo (git instructions below)", homeErr)
122+
}
123+
return "", fmt.Errorf("PasteLocal grok skills source not found. Run from a clone of the official repo: git clone https://github.com/Zen-Open-Source/PasteLocal && cd PasteLocal && ./pastelocal grok install-skills (or go run ./cmd/pastelocal grok install-skills)")
124+
}
125+
126+
func hasGrokSkill(dir string) bool {
127+
p := filepath.Join(dir, "paste", "SKILL.md")
128+
data, err := os.ReadFile(p)
129+
if err != nil {
130+
return false
131+
}
132+
return bytes.Contains(data, canonicalPasteSkillHeader)
133+
}

cmd/pastelocal/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func init() {
5656
rootCmd.AddGroup(&cobra.Group{ID: "daemon", Title: "Daemon Management"})
5757
rootCmd.AddGroup(&cobra.Group{ID: "host", Title: "Host Management"})
5858
rootCmd.AddGroup(&cobra.Group{ID: "diagnostic", Title: "Diagnostics"})
59+
rootCmd.AddGroup(&cobra.Group{ID: "grok", Title: "Grok Integration"})
5960
}
6061

6162
// fail prints a single-line error to stderr and returns an error for cobra.

0 commit comments

Comments
 (0)