termsearch 0.3.4 → 0.3.5

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/bin/termsearch.js CHANGED
@@ -209,7 +209,7 @@ async function cmdStart(flags) {
209
209
  }
210
210
 
211
211
  writeFileSync(paths.pid, String(child.pid));
212
- ok(`Started (PID ${child.pid})`);
212
+ ok(`TermSearch v${VERSION} started (PID ${child.pid})`);
213
213
  info(`${BOLD}${getUrl()}${RESET}`);
214
214
  info(`Logs: ${paths.log}`);
215
215
  }
@@ -242,10 +242,11 @@ async function cmdRestart(flags) {
242
242
  await cmdStart(flags);
243
243
  }
244
244
 
245
- function cmdStatus() {
245
+ async function cmdStatus() {
246
246
  const { running, pid } = getStatus();
247
247
  const paths = getPaths();
248
248
  console.log('');
249
+ console.log(` ${BOLD}TermSearch v${VERSION}${RESET}`);
249
250
  if (running) {
250
251
  ok(`${BOLD}Running${RESET} (PID ${pid})`);
251
252
  info(`${getUrl()}`);
@@ -255,6 +256,7 @@ function cmdStatus() {
255
256
  warn('Stopped');
256
257
  info(`Run ${BOLD}termsearch start${RESET} to start`);
257
258
  }
259
+ await printUpdateHint();
258
260
  console.log('');
259
261
  }
260
262
 
@@ -333,6 +335,9 @@ async function cmdDoctor() {
333
335
  } catch (e) { err(`HTTP: cannot reach ${getUrl()} — ${e.message}`); allOk = false; }
334
336
  }
335
337
 
338
+ // npm update check
339
+ await printUpdateHint();
340
+
336
341
  console.log('');
337
342
  if (allOk) { ok(`${GREEN}All checks passed${RESET}`); }
338
343
  else { warn('Some checks failed — see above'); }
@@ -428,6 +433,39 @@ ${BOLD}URL:${RESET} http://localhost:3000
428
433
  `);
429
434
  }
430
435
 
436
+ // ─── Update check ─────────────────────────────────────────────────────────
437
+
438
+ async function checkNpmUpdate() {
439
+ try {
440
+ const ac = new AbortController();
441
+ setTimeout(() => ac.abort(), 4000);
442
+ const r = await fetch('https://registry.npmjs.org/termsearch/latest', { signal: ac.signal });
443
+ if (!r.ok) return null;
444
+ const data = await r.json();
445
+ const latest = data.version;
446
+ if (!latest) return null;
447
+ if (latest === VERSION) return { upToDate: true, latest };
448
+ // Simple semver compare: split, compare numerically
449
+ const cur = VERSION.split('.').map(Number);
450
+ const lat = latest.split('.').map(Number);
451
+ const newer = lat[0] > cur[0] || (lat[0] === cur[0] && lat[1] > cur[1]) || (lat[0] === cur[0] && lat[1] === cur[1] && lat[2] > cur[2]);
452
+ return { upToDate: !newer, latest };
453
+ } catch {
454
+ return null;
455
+ }
456
+ }
457
+
458
+ async function printUpdateHint() {
459
+ const update = await checkNpmUpdate();
460
+ if (!update) return;
461
+ if (update.upToDate) {
462
+ ok(`Up to date (v${VERSION})`);
463
+ } else {
464
+ warn(`Update available: v${VERSION} → v${update.latest}`);
465
+ info(`Run ${BOLD}npm install -g termsearch${RESET} to update`);
466
+ }
467
+ }
468
+
431
469
  // ─── Utilities ────────────────────────────────────────────────────────────
432
470
 
433
471
  function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
@@ -267,7 +267,6 @@ const AI_PRESETS = [
267
267
  { id: 'chutes', label: 'Chutes.ai TEE', api_base: 'https://llm.chutes.ai/v1', keyRequired: true, defaultModel: 'deepseek-ai/DeepSeek-V3.2-TEE' },
268
268
  { id: 'anthropic',label: 'Anthropic', api_base: 'https://api.anthropic.com/v1', keyRequired: true, defaultModel: 'claude-3-5-haiku-latest' },
269
269
  { id: 'openai', label: 'OpenAI', api_base: 'https://api.openai.com/v1', keyRequired: true, defaultModel: 'gpt-4o-mini' },
270
- { id: 'openrouter', label: 'OpenRoute/OpenRouter', api_base: 'https://openrouter.ai/api/v1', keyRequired: true, defaultModel: 'openai/gpt-4o-mini' },
271
270
  ];
272
271
 
273
272
  const ENGINE_GROUPS = [
@@ -282,7 +281,7 @@ const ENGINE_GROUPS = [
282
281
 
283
282
  const ENGINE_PRESETS = [
284
283
  { id: 'all', label: 'All', engines: [] },
285
- { id: 'balanced', label: 'Balanced', engines: ['duckduckgo', 'wikipedia', 'bing', 'startpage', 'github', 'reddit', 'youtube'] },
284
+ { id: 'balanced', label: 'Balanced', engines: ['duckduckgo', 'wikipedia', 'bing', 'brave', 'github', 'reddit', 'youtube'] },
286
285
  { id: 'github', label: 'GitHub Focus', engines: ['github-api', 'github', 'duckduckgo', 'wikipedia'] },
287
286
  ];
288
287
 
@@ -1054,12 +1053,12 @@ function renderApp() {
1054
1053
  const mobileBar = el('div', { className: 'mobile-bar' },
1055
1054
  el('div', { className: 'mobile-bar-search' }, SearchForm(state.query, (q, cat) => { state.query = q; doSearch(q, cat); })),
1056
1055
  mobileTabs,
1057
- el('div', { className: 'mobile-bar-engine' }, EnginePicker()),
1058
1056
  el('div', { className: 'mobile-bar-row' },
1059
1057
  el('div', {
1060
1058
  className: 'mobile-logo',
1061
1059
  onClick: () => { state.query = ''; state.category = 'web'; navigate('#/'); renderApp(); },
1062
1060
  }, 'Term', el('strong', {}, 'Search')),
1061
+ EnginePicker(),
1063
1062
  LangPicker(),
1064
1063
  el('button', { className: 'btn-icon', title: 'Settings', onClick: () => navigate('#/settings') }, iconEl('settings')),
1065
1064
  el('button', { className: 'btn-icon', title: 'Toggle theme', onClick: toggleTheme }, iconEl('theme')),
@@ -1161,6 +1160,9 @@ async function renderSettings() {
1161
1160
  const brave = cfg.brave || {};
1162
1161
  const mojeek = cfg.mojeek || {};
1163
1162
  const searxng = cfg.searxng || {};
1163
+ const yandexCfg = cfg.yandex || {};
1164
+ const ahmiaCfg = cfg.ahmia || {};
1165
+ const marginaliaCfg = cfg.marginalia || {};
1164
1166
  const detectedPreset = detectPresetFromBase(ai.api_base);
1165
1167
 
1166
1168
  const header = el('div', { className: 'header' },
@@ -1338,6 +1340,9 @@ async function renderSettings() {
1338
1340
  brave: { enabled: isChecked('brave-enabled') },
1339
1341
  mojeek: { enabled: isChecked('mojeek-enabled') },
1340
1342
  searxng:{ url: val('searxng-url'), enabled: isChecked('searxng-enabled') },
1343
+ yandex: { enabled: isChecked('yandex-enabled') },
1344
+ ahmia: { enabled: isChecked('ahmia-enabled') },
1345
+ marginalia: { enabled: isChecked('marginalia-enabled') },
1341
1346
  };
1342
1347
  if (aiKey) update.ai.api_key = aiKey;
1343
1348
  if (braveKey) update.brave.api_key = braveKey;
@@ -1470,7 +1475,7 @@ async function renderSettings() {
1470
1475
  el('label', { className: 'form-label', for: 'ai-base' }, 'API Endpoint'),
1471
1476
  makeInput('ai-base', ai.api_base, 'http://localhost:11434/v1'),
1472
1477
  el('div', { className: 'form-hint' },
1473
- 'Included presets: LocalHost (Ollama · LM Studio · llama.cpp) · Chutes.ai TEE · Anthropic · OpenAI · OpenRoute/OpenRouter',
1478
+ 'Included presets: LocalHost (Ollama · LM Studio · llama.cpp) · Chutes.ai TEE · Anthropic · OpenAI',
1474
1479
  el('br', {}),
1475
1480
  'You can also keep custom OpenAI-compatible endpoints.',
1476
1481
  ),
@@ -1563,7 +1568,7 @@ async function renderSettings() {
1563
1568
  ),
1564
1569
 
1565
1570
  // SearXNG
1566
- el('div', { style: 'padding:10px 0' },
1571
+ el('div', { style: 'padding:10px 0;border-bottom:1px solid var(--border2)' },
1567
1572
  el('div', { className: 'toggle-row' },
1568
1573
  el('span', { className: 'toggle-label' }, 'SearXNG (self-hosted)'),
1569
1574
  el('label', { className: 'toggle' },
@@ -1578,6 +1583,33 @@ async function renderSettings() {
1578
1583
  el('div', { id: 'provider-test-searxng', style: 'display:none' }),
1579
1584
  ),
1580
1585
 
1586
+ // Uncensored / Alternative
1587
+ el('div', { style: 'padding:10px 0' },
1588
+ el('div', { style: 'font-size:11px;color:var(--text2);margin-bottom:8px;letter-spacing:0.04em;text-transform:uppercase' }, 'Uncensored / Alternative'),
1589
+ el('div', { className: 'toggle-row' },
1590
+ el('span', { className: 'toggle-label' }, 'Yandex (HTML scraper, no key)'),
1591
+ el('label', { className: 'toggle' },
1592
+ el('input', { type: 'checkbox', id: 'yandex-enabled', ...(yandexCfg.enabled !== false ? { checked: '' } : {}) }),
1593
+ el('span', { className: 'toggle-slider' }),
1594
+ ),
1595
+ ),
1596
+ el('div', { className: 'toggle-row', style: 'margin-top:6px' },
1597
+ el('span', { className: 'toggle-label' }, 'Ahmia (Tor index, no key)'),
1598
+ el('label', { className: 'toggle' },
1599
+ el('input', { type: 'checkbox', id: 'ahmia-enabled', ...(ahmiaCfg.enabled !== false ? { checked: '' } : {}) }),
1600
+ el('span', { className: 'toggle-slider' }),
1601
+ ),
1602
+ ),
1603
+ el('div', { className: 'toggle-row', style: 'margin-top:6px' },
1604
+ el('span', { className: 'toggle-label' }, 'Marginalia (indie index, no key)'),
1605
+ el('label', { className: 'toggle' },
1606
+ el('input', { type: 'checkbox', id: 'marginalia-enabled', ...(marginaliaCfg.enabled !== false ? { checked: '' } : {}) }),
1607
+ el('span', { className: 'toggle-slider' }),
1608
+ ),
1609
+ ),
1610
+ el('div', { className: 'form-hint', style: 'margin-top:6px' }, 'Zero-config scraper engines. May be blocked by CAPTCHA under heavy use.'),
1611
+ ),
1612
+
1581
1613
  el('div', { style: 'margin-top:12px;display:flex;align-items:center;gap:8px' },
1582
1614
  el('button', { className: 'btn btn-primary', onClick: saveSettings }, 'Save All'),
1583
1615
  saveAlertEl,
@@ -962,13 +962,13 @@ a:hover { color: var(--link-h); }
962
962
  scrollbar-width: none;
963
963
  }
964
964
  .mobile-bar-tabs::-webkit-scrollbar { display: none; }
965
- .mobile-bar-engine { padding: 0 12px 4px; }
966
965
  .mobile-bar-row {
967
966
  display: flex;
968
967
  align-items: center;
969
968
  gap: 6px;
970
969
  padding: 4px 12px 2px;
971
970
  }
971
+ .mobile-bar-row .engine-picker { flex-shrink: 0; }
972
972
  .mobile-bar-row .lang-wrap { margin-left: auto; }
973
973
 
974
974
  /* ─── Responsive ──────────────────────────────────────────────────────────── */
@@ -978,8 +978,7 @@ a:hover { color: var(--link-h); }
978
978
  .main { padding-bottom: calc(20px + var(--mobile-bar-height, 0px) + env(safe-area-inset-bottom, 0px)); }
979
979
 
980
980
  .cat-tab { font-size: 10px; padding: 4px 8px; }
981
- .mobile-bar-engine .engine-picker { width: 100%; margin-left: 0; }
982
- .mobile-bar-engine .engine-picker-summary { width: 100%; justify-content: space-between; }
981
+ .mobile-bar-row .engine-picker-summary { font-size: 11px; padding: 3px 6px; }
983
982
  .engine-picker-body { width: calc(100vw - 24px); right: -6px; bottom: calc(100% + 8px); top: auto; }
984
983
  .logo-text { font-size: 15px; }
985
984
  .home-logo { font-size: 40px; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termsearch",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Personal search engine for Termux/Linux/macOS — zero-config, privacy-first, AI-optional",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,9 +17,15 @@ function ok(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
17
17
  function warn(msg) { console.log(` ${YELLOW}⚠${RESET} ${msg}`); }
18
18
  function info(msg) { console.log(` ${CYAN}→${RESET} ${msg}`); }
19
19
 
20
+ let VERSION = '0.0.0';
21
+ try {
22
+ const pkgPath = new URL('../package.json', import.meta.url);
23
+ VERSION = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version || VERSION;
24
+ } catch { /* ignore */ }
25
+
20
26
  try {
21
27
  console.log('');
22
- console.log(`${BOLD} TermSearch — post-install check${RESET}`);
28
+ console.log(`${BOLD} TermSearch v${VERSION}${RESET} — post-install check`);
23
29
  console.log('');
24
30
 
25
31
  // ── Node.js version ──────────────────────────────────────────────────────
@@ -76,7 +82,7 @@ try {
76
82
  }
77
83
 
78
84
  console.log('');
79
- info(`Run ${BOLD}termsearch${RESET}${CYAN} then open http://localhost:3000`);
85
+ info(`Run ${BOLD}termsearch${RESET}${CYAN} to start v${VERSION} → http://localhost:3000`);
80
86
  console.log('');
81
87
 
82
88
  } catch {