termsearch 0.3.7 → 0.3.8
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.
- package/README.md +111 -145
- package/bin/termsearch.js +1 -1
- package/frontend/dist/app.js +51 -7
- package/frontend/dist/style.css +25 -3
- package/package.json +1 -1
- package/src/api/routes.js +14 -1
- package/src/server.js +14 -1
package/README.md
CHANGED
|
@@ -1,187 +1,167 @@
|
|
|
1
|
-
# TermSearch
|
|
1
|
+
# TermSearch
|
|
2
2
|
|
|
3
|
-
[](#)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
|
-
[](#)
|
|
7
7
|
[](https://www.npmjs.com/package/termsearch)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
Zero external dependencies, no Docker, no Python. AI is optional and configured entirely from the browser.
|
|
9
|
+
**Personal search engine.** One command install, zero config, privacy-first.
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
No Docker, no Python, no API keys required. AI is optional. Everything runs from a single `npm install`.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
- Built-in GitHub Search API fallback (`github-api`) and selectable engine mix from UI
|
|
16
|
-
- Progressive enhancement: add Brave/Mojeek API keys, AI endpoints, or SearXNG when needed
|
|
17
|
-
- Search history persistence (opt-in toggle in Settings)
|
|
18
|
-
- Social profile scanner: GitHub, Bluesky, Reddit, Twitter/X, Instagram, YouTube, LinkedIn, TikTok, Telegram, Facebook
|
|
19
|
-
- Torrent search: The Pirate Bay + 1337x with direct magnet extraction
|
|
20
|
-
- Social search: Bluesky posts/actors + GDELT news
|
|
21
|
-
- AI-powered 2-phase agentic summaries via any OpenAI-compatible endpoint
|
|
22
|
-
- Vanilla HTML/CSS/JS frontend (~280KB, no build step, no framework)
|
|
23
|
-
- Boot autostart: Termux:Boot, systemd --user, or launchd (macOS)
|
|
13
|
+
## Install & Run
|
|
24
14
|
|
|
25
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g termsearch
|
|
17
|
+
termsearch
|
|
18
|
+
```
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
- Core is MIT — zero required API keys
|
|
29
|
-
- AI features are optional, configured via Settings page in browser
|
|
30
|
-
- Tested on: Ubuntu 24.04, Termux (Android 15/16)
|
|
31
|
-
- macOS: compatible (launchd autostart), untested in production
|
|
20
|
+
Opens `http://localhost:3000`. That's it.
|
|
32
21
|
|
|
33
|
-
##
|
|
22
|
+
## What You Get
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
**Search** — DuckDuckGo + Wikipedia out of the box. Add Brave, Mojeek, Yandex, Marginalia, Ahmia, or your own SearXNG for more coverage. Engine picker lets you mix and match per-search.
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
npm install -g termsearch
|
|
39
|
-
```
|
|
26
|
+
**AI Summaries** — Connect any OpenAI-compatible endpoint (Ollama, LM Studio, llama.cpp, Chutes.ai, Anthropic, OpenAI). 2-phase agentic flow: AI picks sources, reads pages, synthesizes an answer. Session memory carries context across queries.
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
**Social Profiler** — Paste a GitHub/Bluesky/Reddit/Twitter URL or @handle, get a profile card with stats, top repos, similar accounts.
|
|
42
29
|
|
|
43
|
-
|
|
44
|
-
termsearch
|
|
45
|
-
```
|
|
30
|
+
**Torrent Search** — The Pirate Bay + 1337x with magnet links, seeders, file sizes.
|
|
46
31
|
|
|
47
|
-
|
|
32
|
+
**Social & News** — Bluesky posts + GDELT articles inline.
|
|
48
33
|
|
|
49
|
-
|
|
34
|
+
## Progressive Enhancement
|
|
35
|
+
|
|
36
|
+
| Level | Config | What works |
|
|
37
|
+
|-------|--------|------------|
|
|
38
|
+
| **0** | None | DuckDuckGo + Wikipedia + Yandex + Marginalia + Ahmia |
|
|
39
|
+
| **1** | API keys (Settings) | + Brave, Mojeek |
|
|
40
|
+
| **2** | AI endpoint (Settings) | + AI summaries, query refinement, session memory |
|
|
41
|
+
| **3** | SearXNG URL | + 40 engines via your SearXNG instance |
|
|
50
42
|
|
|
51
43
|
## CLI
|
|
52
44
|
|
|
53
|
-
```
|
|
54
|
-
termsearch
|
|
55
|
-
termsearch start
|
|
56
|
-
termsearch start --fg
|
|
57
|
-
termsearch stop
|
|
58
|
-
termsearch
|
|
59
|
-
termsearch
|
|
60
|
-
termsearch
|
|
61
|
-
termsearch
|
|
62
|
-
termsearch
|
|
63
|
-
termsearch
|
|
64
|
-
termsearch autostart enable # start at boot (Termux:Boot / systemd / launchd)
|
|
65
|
-
termsearch autostart disable # disable autostart
|
|
66
|
-
termsearch help # full command reference
|
|
45
|
+
```
|
|
46
|
+
termsearch start + open browser
|
|
47
|
+
termsearch start background daemon
|
|
48
|
+
termsearch start --fg foreground (live logs)
|
|
49
|
+
termsearch stop / restart manage daemon
|
|
50
|
+
termsearch status version, PID, URL, update check
|
|
51
|
+
termsearch doctor full health check + npm update check
|
|
52
|
+
termsearch logs [-n 100] server log tail
|
|
53
|
+
termsearch open open browser
|
|
54
|
+
termsearch autostart enable boot start (Termux:Boot / systemd / launchd)
|
|
55
|
+
termsearch help full reference
|
|
67
56
|
```
|
|
68
57
|
|
|
69
|
-
Options:
|
|
58
|
+
Options: `--port=3000` `--host=127.0.0.1` `--data-dir=~/.termsearch/`
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
--port=<port> # default: 3000
|
|
73
|
-
--host=<host> # default: 127.0.0.1
|
|
74
|
-
--data-dir=<path> # default: ~/.termsearch/
|
|
75
|
-
```
|
|
60
|
+
## AI Presets
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
Configure in **Settings > AI** from the browser. Presets auto-fill endpoint and model:
|
|
63
|
+
|
|
64
|
+
| Preset | Endpoint | Key | Default Model |
|
|
65
|
+
|--------|----------|-----|---------------|
|
|
66
|
+
| Ollama | `localhost:11434/v1` | no | `qwen3.5:4b` |
|
|
67
|
+
| LM Studio | `localhost:1234/v1` | no | — |
|
|
68
|
+
| llama.cpp | `localhost:8080/v1` | no | — |
|
|
69
|
+
| Chutes.ai TEE | `llm.chutes.ai/v1` | yes | `DeepSeek-V3.2-TEE` |
|
|
70
|
+
| Anthropic | `api.anthropic.com/v1` | yes | `claude-3-5-haiku-latest` |
|
|
71
|
+
| OpenAI | `api.openai.com/v1` | yes | `gpt-4o-mini` |
|
|
72
|
+
| Custom | any OpenAI-compatible | optional | — |
|
|
73
|
+
|
|
74
|
+
Load Models button auto-discovers available models from the endpoint.
|
|
75
|
+
|
|
76
|
+
## Search Engines
|
|
77
|
+
|
|
78
|
+
**Zero-config** (no API key): DuckDuckGo, Wikipedia, GitHub, Yandex, Ahmia, Marginalia
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|-------|-------------|---------|
|
|
81
|
-
| **0** (zero-config) | None | DuckDuckGo + Wikipedia — works immediately |
|
|
82
|
-
| **1** (API keys) | Brave/Mojeek key via Settings | Better and more diverse results |
|
|
83
|
-
| **2** (AI) | Any OpenAI-compatible endpoint | Summaries, query refinement |
|
|
84
|
-
| **3** (power user) | Own SearXNG instance | 40+ search engines |
|
|
80
|
+
**API key** (toggle in Settings): Brave Search, Mojeek
|
|
85
81
|
|
|
86
|
-
|
|
82
|
+
**Self-hosted**: SearXNG (proxy to 40+ engines)
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
**Selectable per-search**: Engine picker icon in the header lets you toggle individual engines, use presets (All / Balanced / GitHub Focus), or pick from groups (Web Core, Uncensored, Code & Dev, Media, Research, Federated, Torrent).
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|----------|----------|-------|-----|
|
|
92
|
-
| **Localhost** (Ollama) | `http://localhost:11434/v1` | `qwen3.5:4b` or any | not required |
|
|
93
|
-
| **Localhost** (LM Studio) | `http://localhost:1234/v1` | your loaded model | not required |
|
|
94
|
-
| **Localhost** (llama.cpp) | `http://localhost:8080/v1` | your loaded model | not required |
|
|
95
|
-
| **Chutes.ai TEE** | `https://llm.chutes.ai/v1` | `deepseek-ai/DeepSeek-V3.2-TEE` | required |
|
|
96
|
-
| **Anthropic** | `https://api.anthropic.com/v1` | `claude-3-5-haiku-latest` | required |
|
|
97
|
-
| **OpenAI** | `https://api.openai.com/v1` | `gpt-4o-mini` | required |
|
|
98
|
-
| **OpenRouter** | `https://openrouter.ai/api/v1` | any listed model | required |
|
|
99
|
-
| **API custom** | any OpenAI-compatible URL | your model | optional |
|
|
86
|
+
## Frontend
|
|
100
87
|
|
|
101
|
-
|
|
88
|
+
Vanilla HTML/CSS/JS — ~350KB total, no build step, no framework, no WASM.
|
|
89
|
+
|
|
90
|
+
- Dark/light theme toggle
|
|
91
|
+
- Mobile-first responsive layout with bottom bar
|
|
92
|
+
- PWA manifest + OpenSearch integration
|
|
93
|
+
- AI panel with progress bar, steps, source pills, session memory, retry
|
|
94
|
+
- Engine picker as compact icon with dropdown
|
|
102
95
|
|
|
103
96
|
## Architecture
|
|
104
97
|
|
|
105
98
|
```
|
|
106
99
|
~/.termsearch/
|
|
107
|
-
config.json
|
|
108
|
-
cache/
|
|
109
|
-
termsearch.pid
|
|
110
|
-
termsearch.log
|
|
100
|
+
config.json settings (saved via browser UI)
|
|
101
|
+
cache/ L1 RAM + L2 disk cache
|
|
102
|
+
termsearch.pid daemon PID
|
|
103
|
+
termsearch.log server log
|
|
111
104
|
|
|
112
105
|
src/
|
|
113
|
-
|
|
106
|
+
server.js Express app (~70 lines)
|
|
107
|
+
config/ manager + defaults + env overrides
|
|
114
108
|
search/
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
ranking.js
|
|
118
|
-
cache.js
|
|
119
|
-
fetch/
|
|
120
|
-
document.js URL fetcher + HTML → readable text + site scan
|
|
121
|
-
ssrf-guard.js SSRF protection
|
|
109
|
+
engine.js fan-out, merge, rank, health tracking
|
|
110
|
+
providers/ ddg, wikipedia, brave, mojeek, searxng, github, yandex, ahmia, marginalia
|
|
111
|
+
ranking.js source diversity ranking
|
|
112
|
+
cache.js tiered L1+L2 cache
|
|
122
113
|
ai/
|
|
123
|
-
orchestrator.js
|
|
124
|
-
summary.js
|
|
125
|
-
query.js
|
|
126
|
-
providers/
|
|
127
|
-
|
|
128
|
-
profiler/
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
autostart/
|
|
136
|
-
manager.js boot autostart (Termux:Boot / systemd / launchd)
|
|
137
|
-
api/
|
|
138
|
-
routes.js all API route handlers
|
|
139
|
-
middleware.js rate limiting, security headers
|
|
140
|
-
server.js Express app setup
|
|
141
|
-
|
|
142
|
-
frontend/dist/ vanilla HTML/CSS/JS SPA (~280KB, no build step)
|
|
114
|
+
orchestrator.js 2-phase agentic summary
|
|
115
|
+
summary.js prompt builder + parser
|
|
116
|
+
query.js query refinement
|
|
117
|
+
providers/ openai-compat universal client
|
|
118
|
+
fetch/ document fetcher + SSRF guard
|
|
119
|
+
profiler/ social profile scanner (10 platforms)
|
|
120
|
+
social/ Bluesky + GDELT + scrapers
|
|
121
|
+
torrent/ TPB + 1337x + magnet extraction
|
|
122
|
+
autostart/ Termux:Boot / systemd / launchd
|
|
123
|
+
api/ routes + middleware
|
|
124
|
+
|
|
125
|
+
frontend/dist/ vanilla SPA
|
|
143
126
|
```
|
|
144
127
|
|
|
145
128
|
## API
|
|
146
129
|
|
|
147
130
|
```
|
|
148
|
-
GET /api/health
|
|
149
|
-
GET /api/
|
|
150
|
-
GET /api/search?q=...
|
|
151
|
-
|
|
152
|
-
POST /api/
|
|
153
|
-
POST /api/
|
|
154
|
-
|
|
155
|
-
GET /api/
|
|
156
|
-
|
|
157
|
-
POST /api/
|
|
158
|
-
POST /api/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
POST /api/config
|
|
162
|
-
POST /api/config/
|
|
163
|
-
GET /api/config/test-provider/:
|
|
164
|
-
GET /api/autostart
|
|
165
|
-
POST /api/autostart
|
|
166
|
-
GET /api/
|
|
131
|
+
GET /api/health status + providers
|
|
132
|
+
GET /api/search?q=... search (JSON)
|
|
133
|
+
GET /api/search-stream?q=... progressive search (SSE)
|
|
134
|
+
POST /api/ai-summary AI summary (SSE streaming)
|
|
135
|
+
POST /api/ai-query AI query refinement
|
|
136
|
+
POST /api/fetch fetch readable content
|
|
137
|
+
GET /api/profiler?q=... social profile scan
|
|
138
|
+
GET /api/social-search?q=... Bluesky + GDELT
|
|
139
|
+
POST /api/torrent-search torrent scrape
|
|
140
|
+
POST /api/magnet magnet extraction
|
|
141
|
+
POST /api/scan site crawl
|
|
142
|
+
GET /api/config config (keys masked)
|
|
143
|
+
POST /api/config update config
|
|
144
|
+
POST /api/config/test-ai test AI connection
|
|
145
|
+
POST /api/config/models list provider models
|
|
146
|
+
GET /api/config/test-provider/:id test search provider
|
|
147
|
+
GET /api/autostart autostart status
|
|
148
|
+
POST /api/autostart toggle autostart
|
|
149
|
+
GET /api/openapi.json OpenAPI spec
|
|
167
150
|
```
|
|
168
151
|
|
|
169
|
-
##
|
|
152
|
+
## Env Vars (optional)
|
|
170
153
|
|
|
171
154
|
```bash
|
|
172
155
|
TERMSEARCH_PORT=3000
|
|
173
156
|
TERMSEARCH_HOST=127.0.0.1
|
|
174
157
|
TERMSEARCH_DATA_DIR=~/.termsearch/
|
|
175
|
-
TERMSEARCH_AI_API_BASE=
|
|
158
|
+
TERMSEARCH_AI_API_BASE=http://localhost:11434/v1
|
|
176
159
|
TERMSEARCH_AI_API_KEY=
|
|
177
|
-
TERMSEARCH_AI_MODEL=
|
|
160
|
+
TERMSEARCH_AI_MODEL=qwen3.5:4b
|
|
178
161
|
TERMSEARCH_BRAVE_API_KEY=
|
|
179
162
|
TERMSEARCH_MOJEEK_API_KEY=
|
|
180
163
|
TERMSEARCH_MARGINALIA_API_KEY=public
|
|
181
|
-
TERMSEARCH_MARGINALIA_API_BASE=https://api2.marginalia-search.com
|
|
182
164
|
TERMSEARCH_SEARXNG_URL=
|
|
183
|
-
TERMSEARCH_GITHUB_TOKEN=
|
|
184
|
-
TERMSEARCH_INSTAGRAM_SESSION=
|
|
185
165
|
```
|
|
186
166
|
|
|
187
167
|
## Termux
|
|
@@ -190,23 +170,9 @@ TERMSEARCH_INSTAGRAM_SESSION=
|
|
|
190
170
|
pkg install nodejs
|
|
191
171
|
npm install -g termsearch
|
|
192
172
|
termsearch
|
|
173
|
+
termsearch autostart enable # optional: start on boot
|
|
193
174
|
```
|
|
194
175
|
|
|
195
|
-
Enable autostart with Termux:Boot (install from F-Droid):
|
|
196
|
-
|
|
197
|
-
```bash
|
|
198
|
-
termsearch autostart enable
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
## Roadmap
|
|
202
|
-
|
|
203
|
-
1. Persistent search stats counter
|
|
204
|
-
2. Engine health tracking and failure classification
|
|
205
|
-
3. Frontend profile viewer panel
|
|
206
|
-
4. Frontend torrent results panel with magnet copy
|
|
207
|
-
5. Agentic user-proxy (`/api/user-proxy`) for custom AI loops
|
|
208
|
-
6. Packaged release on npm registry
|
|
209
|
-
|
|
210
176
|
## License
|
|
211
177
|
|
|
212
178
|
MIT — Copyright (c) 2026 Davide A. Guglielmi
|
package/bin/termsearch.js
CHANGED
|
@@ -32,7 +32,7 @@ function info(msg) { console.log(` ${CYAN}→${RESET} ${msg}`); }
|
|
|
32
32
|
|
|
33
33
|
// ─── Package version ──────────────────────────────────────────────────────
|
|
34
34
|
|
|
35
|
-
let VERSION = '0.3.
|
|
35
|
+
let VERSION = '0.3.8';
|
|
36
36
|
try { VERSION = JSON.parse(readFileSync(PKG_PATH, 'utf8')).version || VERSION; } catch { /* ignore */ }
|
|
37
37
|
|
|
38
38
|
// ─── Data dir + paths ─────────────────────────────────────────────────────
|
package/frontend/dist/app.js
CHANGED
|
@@ -268,7 +268,6 @@ const AI_PRESETS = [
|
|
|
268
268
|
{ id: 'chutes', label: 'Chutes.ai TEE', api_base: 'https://llm.chutes.ai/v1', keyRequired: true, defaultModel: 'deepseek-ai/DeepSeek-V3.2-TEE' },
|
|
269
269
|
{ id: 'anthropic',label: 'Anthropic', api_base: 'https://api.anthropic.com/v1', keyRequired: true, defaultModel: 'claude-3-5-haiku-latest' },
|
|
270
270
|
{ id: 'openai', label: 'OpenAI', api_base: 'https://api.openai.com/v1', keyRequired: true, defaultModel: 'gpt-4o-mini' },
|
|
271
|
-
{ id: 'openrouter', label: 'OpenRoute/OpenRouter', api_base: 'https://openrouter.ai/api/v1', keyRequired: true, defaultModel: 'openai/gpt-4o-mini' },
|
|
272
271
|
];
|
|
273
272
|
|
|
274
273
|
const ENGINE_GROUPS = [
|
|
@@ -387,6 +386,20 @@ function EnginePicker(opts = {}) {
|
|
|
387
386
|
));
|
|
388
387
|
|
|
389
388
|
details.append(summary, body);
|
|
389
|
+
|
|
390
|
+
// Close on click-outside
|
|
391
|
+
details.addEventListener('toggle', () => {
|
|
392
|
+
if (!details.open) return;
|
|
393
|
+
const onClickOutside = (e) => {
|
|
394
|
+
if (!details.contains(e.target)) {
|
|
395
|
+
details.open = false;
|
|
396
|
+
document.removeEventListener('click', onClickOutside, true);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
// Defer to avoid catching the same click that opened it
|
|
400
|
+
requestAnimationFrame(() => document.addEventListener('click', onClickOutside, true));
|
|
401
|
+
});
|
|
402
|
+
|
|
390
403
|
return details;
|
|
391
404
|
}
|
|
392
405
|
|
|
@@ -1029,7 +1042,7 @@ function renderApp() {
|
|
|
1029
1042
|
el('div', { className: 'header-nav' },
|
|
1030
1043
|
LangPicker(),
|
|
1031
1044
|
EnginePicker({ compact: true }),
|
|
1032
|
-
el('button', { className: 'btn-icon', title: 'Settings',
|
|
1045
|
+
el('button', { className: 'btn-icon', title: 'Settings', onClick: () => navigate('#/settings') }, iconEl('settings')),
|
|
1033
1046
|
el('button', { className: 'btn-icon', title: 'Toggle theme', onClick: toggleTheme }, iconEl('theme')),
|
|
1034
1047
|
),
|
|
1035
1048
|
);
|
|
@@ -1067,9 +1080,11 @@ function renderApp() {
|
|
|
1067
1080
|
onClick: () => { state.query = ''; state.category = 'web'; navigate('#/'); renderApp(); },
|
|
1068
1081
|
}, 'Term', el('strong', {}, 'Search')),
|
|
1069
1082
|
LangPicker(),
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1083
|
+
el('div', { className: 'mobile-bar-actions' },
|
|
1084
|
+
EnginePicker({ compact: true }),
|
|
1085
|
+
el('button', { className: 'btn-icon', title: 'Settings', onClick: () => navigate('#/settings') }, iconEl('settings')),
|
|
1086
|
+
el('button', { className: 'btn-icon', title: 'Toggle theme', onClick: toggleTheme }, iconEl('theme')),
|
|
1087
|
+
),
|
|
1073
1088
|
),
|
|
1074
1089
|
);
|
|
1075
1090
|
|
|
@@ -1122,6 +1137,33 @@ function renderApp() {
|
|
|
1122
1137
|
}
|
|
1123
1138
|
|
|
1124
1139
|
// ─── Homepage ─────────────────────────────────────────────────────────────
|
|
1140
|
+
function addToBrowser() {
|
|
1141
|
+
// Try legacy Firefox API
|
|
1142
|
+
try {
|
|
1143
|
+
if (window.external?.AddSearchProvider) {
|
|
1144
|
+
window.external.AddSearchProvider(location.origin + '/opensearch.xml');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
} catch {}
|
|
1148
|
+
// Show inline hint
|
|
1149
|
+
const existing = document.querySelector('.add-browser-hint');
|
|
1150
|
+
if (existing) { existing.remove(); return; }
|
|
1151
|
+
const hint = el('div', { className: 'add-browser-hint' },
|
|
1152
|
+
el('strong', {}, 'Add TermSearch to your browser:'),
|
|
1153
|
+
el('div', { style: 'margin-top:6px' },
|
|
1154
|
+
el('span', { style: 'color:var(--text2)' }, 'Firefox'), ' — click the address bar, look for "Add TermSearch"',
|
|
1155
|
+
),
|
|
1156
|
+
el('div', { style: 'margin-top:3px' },
|
|
1157
|
+
el('span', { style: 'color:var(--text2)' }, 'Chrome'), ' — Settings → Search engine → Manage → Add',
|
|
1158
|
+
),
|
|
1159
|
+
el('div', { style: 'margin-top:3px;font-family:var(--font-mono);font-size:10px;color:var(--text3);word-break:break-all' },
|
|
1160
|
+
`URL: ${location.origin}/#/?q=%s`,
|
|
1161
|
+
),
|
|
1162
|
+
);
|
|
1163
|
+
document.querySelector('.home-actions')?.after(hint);
|
|
1164
|
+
setTimeout(() => hint.remove(), 12000);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1125
1167
|
function renderHome(app) {
|
|
1126
1168
|
const home = el('div', { className: 'home' },
|
|
1127
1169
|
el('div', { className: 'home-logo' }, 'Term', el('strong', {}, 'Search')),
|
|
@@ -1134,6 +1176,8 @@ function renderHome(app) {
|
|
|
1134
1176
|
LangPicker(),
|
|
1135
1177
|
el('button', { className: 'btn', onClick: () => navigate('#/settings') }, iconEl('settings'), ' Settings'),
|
|
1136
1178
|
el('button', { className: 'btn', onClick: toggleTheme }, iconEl('theme'), ' Theme'),
|
|
1179
|
+
el('button', { className: 'btn', onClick: addToBrowser, title: 'Add as browser search engine' }, iconEl('search'), ' Add to browser'),
|
|
1180
|
+
el('a', { className: 'btn', href: 'https://github.com/DioNanos/termsearch', target: '_blank', rel: 'noopener noreferrer' }, iconEl('github'), ' GitHub'),
|
|
1137
1181
|
),
|
|
1138
1182
|
);
|
|
1139
1183
|
|
|
@@ -1483,7 +1527,7 @@ async function renderSettings() {
|
|
|
1483
1527
|
el('label', { className: 'form-label', for: 'ai-base' }, 'API Endpoint'),
|
|
1484
1528
|
makeInput('ai-base', ai.api_base, 'http://localhost:11434/v1'),
|
|
1485
1529
|
el('div', { className: 'form-hint' },
|
|
1486
|
-
'Included presets: LocalHost (Ollama · LM Studio · llama.cpp) · Chutes.ai TEE · Anthropic · OpenAI
|
|
1530
|
+
'Included presets: LocalHost (Ollama · LM Studio · llama.cpp) · Chutes.ai TEE · Anthropic · OpenAI',
|
|
1487
1531
|
el('br', {}),
|
|
1488
1532
|
'You can also keep custom OpenAI-compatible endpoints.',
|
|
1489
1533
|
),
|
|
@@ -1627,7 +1671,7 @@ async function renderSettings() {
|
|
|
1627
1671
|
// Server info
|
|
1628
1672
|
el('div', { className: 'settings-section' },
|
|
1629
1673
|
el('h2', {}, 'Server Info'),
|
|
1630
|
-
el('div', { className: 'info-row' }, el('span', { className: 'info-key' }, 'Version'), el('span', { className: 'info-val' }, health?.version || '0.3.
|
|
1674
|
+
el('div', { className: 'info-row' }, el('span', { className: 'info-key' }, 'Version'), el('span', { className: 'info-val' }, health?.version || '0.3.8')),
|
|
1631
1675
|
el('div', { className: 'info-row' }, el('span', { className: 'info-key' }, 'Active providers'), el('span', { className: 'info-val' }, (health?.providers || []).join(', ') || 'none')),
|
|
1632
1676
|
el('div', { className: 'info-row' }, el('span', { className: 'info-key' }, 'AI'), el('span', { className: 'info-val' }, health?.ai_enabled ? `enabled (${health.ai_model})` : 'not configured')),
|
|
1633
1677
|
el('div', { className: 'info-row' }, el('span', { className: 'info-key' }, 'GitHub'), el('a', { href: 'https://github.com/DioNanos/termsearch', target: '_blank', className: 'info-val', style: 'color:var(--link)' }, 'DioNanos/termsearch')),
|
package/frontend/dist/style.css
CHANGED
|
@@ -189,12 +189,13 @@ a:hover { color: var(--link-h); }
|
|
|
189
189
|
justify-content: center;
|
|
190
190
|
background: transparent;
|
|
191
191
|
color: var(--text3);
|
|
192
|
-
border
|
|
192
|
+
border: 1px solid transparent;
|
|
193
193
|
position: relative;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
transition: color 0.15s;
|
|
194
196
|
}
|
|
195
197
|
.engine-picker-summary-icon:hover {
|
|
196
|
-
|
|
197
|
-
color: var(--text2);
|
|
198
|
+
color: var(--text);
|
|
198
199
|
}
|
|
199
200
|
.engine-filter-icon {
|
|
200
201
|
display: inline-flex;
|
|
@@ -934,6 +935,20 @@ a:hover { color: var(--link-h); }
|
|
|
934
935
|
/* ─── No results ──────────────────────────────────────────────────────────── */
|
|
935
936
|
.no-results { text-align: center; padding: 40px 20px; color: var(--text3); font-size: 14px; }
|
|
936
937
|
|
|
938
|
+
/* ─── Add to browser hint ─────────────────────────────────────────────────── */
|
|
939
|
+
.add-browser-hint {
|
|
940
|
+
max-width: 380px;
|
|
941
|
+
margin: 16px auto 0;
|
|
942
|
+
padding: 12px 16px;
|
|
943
|
+
border-radius: var(--radius);
|
|
944
|
+
background: rgba(30,27,75,0.35);
|
|
945
|
+
border: 1px solid rgba(109,40,217,0.25);
|
|
946
|
+
color: var(--text3);
|
|
947
|
+
font-size: 11px;
|
|
948
|
+
line-height: 1.6;
|
|
949
|
+
animation: fadeIn 0.2s ease;
|
|
950
|
+
}
|
|
951
|
+
|
|
937
952
|
/* ─── Footer ──────────────────────────────────────────────────────────────── */
|
|
938
953
|
.footer {
|
|
939
954
|
border-top: 1px solid var(--border2);
|
|
@@ -1005,6 +1020,12 @@ a:hover { color: var(--link-h); }
|
|
|
1005
1020
|
padding: 4px 12px 2px;
|
|
1006
1021
|
}
|
|
1007
1022
|
.mobile-bar-row .lang-wrap { margin-left: auto; }
|
|
1023
|
+
.mobile-bar-actions {
|
|
1024
|
+
display: flex;
|
|
1025
|
+
align-items: center;
|
|
1026
|
+
gap: 4px;
|
|
1027
|
+
flex-shrink: 0;
|
|
1028
|
+
}
|
|
1008
1029
|
|
|
1009
1030
|
/* ─── Responsive ──────────────────────────────────────────────────────────── */
|
|
1010
1031
|
@media (max-width: 640px) {
|
|
@@ -1014,6 +1035,7 @@ a:hover { color: var(--link-h); }
|
|
|
1014
1035
|
|
|
1015
1036
|
.cat-tab { font-size: 10px; padding: 4px 8px; }
|
|
1016
1037
|
.mobile-bar-row .engine-picker-summary-icon { width: 30px; height: 30px; }
|
|
1038
|
+
.mobile-bar-actions .engine-picker-summary-icon { width: 30px; height: 30px; }
|
|
1017
1039
|
.engine-picker-body {
|
|
1018
1040
|
position: fixed;
|
|
1019
1041
|
left: 10px;
|
package/package.json
CHANGED
package/src/api/routes.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// All API route handlers
|
|
2
2
|
|
|
3
3
|
import express from 'express';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
4
7
|
import { search, searchStream, getEnabledProviders, getDocCache, ALLOWED_ENGINES } from '../search/engine.js';
|
|
5
8
|
import { batchFetch, fetchReadableDocument } from '../fetch/document.js';
|
|
6
9
|
import { generateSummary, testConnection } from '../ai/orchestrator.js';
|
|
@@ -11,7 +14,17 @@ import { detectProfileTarget, scanProfile, PROFILER_PLATFORMS } from '../profile
|
|
|
11
14
|
import { fetchBlueskyPosts, fetchBlueskyActors, fetchGdeltArticles } from '../social/search.js';
|
|
12
15
|
import { scrapeTPB, scrape1337x, extractMagnetFromUrl } from '../torrent/scrapers.js';
|
|
13
16
|
|
|
14
|
-
const
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
const APP_VERSION = (() => {
|
|
20
|
+
try {
|
|
21
|
+
const pkgPath = path.join(__dirname, '../../package.json');
|
|
22
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
23
|
+
return String(pkg.version || '0.0.0');
|
|
24
|
+
} catch {
|
|
25
|
+
return '0.0.0';
|
|
26
|
+
}
|
|
27
|
+
})();
|
|
15
28
|
const ALLOWED_CATEGORIES = new Set(['web', 'images', 'news']);
|
|
16
29
|
const ALLOWED_LANGS = new Set(['auto', 'it-IT', 'en-US', 'es-ES', 'fr-FR', 'de-DE', 'pt-PT', 'ru-RU', 'zh-CN', 'ja-JP']);
|
|
17
30
|
|
package/src/server.js
CHANGED
|
@@ -36,14 +36,27 @@ app.use(router);
|
|
|
36
36
|
|
|
37
37
|
// Serve frontend static files
|
|
38
38
|
app.use(express.static(FRONTEND_DIST, {
|
|
39
|
-
maxAge:
|
|
39
|
+
maxAge: 0,
|
|
40
40
|
etag: true,
|
|
41
41
|
index: 'index.html',
|
|
42
|
+
setHeaders: (res, filePath) => {
|
|
43
|
+
const base = path.basename(filePath);
|
|
44
|
+
if (base === 'index.html' || base === 'app.js' || base === 'style.css') {
|
|
45
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
46
|
+
res.setHeader('Pragma', 'no-cache');
|
|
47
|
+
res.setHeader('Expires', '0');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
res.setHeader('Cache-Control', 'public, max-age=3600');
|
|
51
|
+
},
|
|
42
52
|
}));
|
|
43
53
|
|
|
44
54
|
// SPA fallback — serve index.html for any non-API route
|
|
45
55
|
app.get('*', (req, res) => {
|
|
46
56
|
applySecurityHeaders(res);
|
|
57
|
+
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
58
|
+
res.setHeader('Pragma', 'no-cache');
|
|
59
|
+
res.setHeader('Expires', '0');
|
|
47
60
|
res.sendFile(path.join(FRONTEND_DIST, 'index.html'));
|
|
48
61
|
});
|
|
49
62
|
|