Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ Linux phones (Librem 5 / PinePhone / Ubuntu Touch). See

## What you get today

- **De-googled new tab** — TronBrowser page, **NeoSearch** as the default private
search (no Google in the omnibox; DuckDuckGo available as an alternative), quick links.
- **De-googled new tab** — TronBrowser page, **Kagi** as the default search
(no Google in the omnibox; NeoSearch, Xprivo, and DuckDuckGo available as
alternatives), quick links.
- **RSS reader** on the new tab — seeded from your OPML; add/remove feeds and
**import/export OPML** in Settings.
- **AI sidebar (bring your own keys)** — Anthropic, OpenAI, Google/Gemini,
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop/extensions/ai-sidebar/newtab.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
<button type="button" id="mode-web" class="mode active" aria-selected="true">Web</button>
<button type="button" id="mode-ai" class="mode" aria-selected="false">AI</button>
</div>
<input id="q" type="text" placeholder="Search NeoSearch…" autocomplete="off" autofocus />
<input id="q" type="text" placeholder="Search Kagi…" autocomplete="off" autofocus />
</form>

<nav class="links">
<a href="https://kagi.com">Kagi</a>
<a href="https://neosearch.org">NeoSearch</a>
<a href="https://www.xprivo.com">Xprivo</a>
<a href="https://duckduckgo.com">DuckDuckGo</a>
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/extensions/ai-sidebar/newtab.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ const DEFAULT_TICKERS = 'SPY, AAPL, NVDA, BTC-USD';
const DEFAULT_LEAGUES = 'nfl, nba';
const splitList = (s) => String(s || '').split(',').map((x) => x.trim()).filter(Boolean);

// --- Search: Web (NeoSearch by default) or AI (sidebar) ---
// --- Search: Web (Kagi by default) or AI (sidebar) ---
const SEARCH_ENGINES = {
kagi: { name: 'Kagi', url: 'https://kagi.com/search?q=' },
neosearch: { name: 'NeoSearch', url: 'https://neosearch.org/?q=' },
xprivo: { name: 'Xprivo', url: 'https://www.xprivo.com/search/?q=' },
ddg: { name: 'DuckDuckGo', url: 'https://duckduckgo.com/?q=' },
Expand All @@ -36,15 +37,15 @@ const TOR_SEARCH_ENGINES = {
excavator: { name: 'Excavator', url: 'http://2fd6cemt4gmccflhm6imvdfvli3nf7zn6rfrwpsy7uhxrgbypvwf5fad.onion/?q=' },
};
let searchMode = 'web';
let searchEngine = 'neosearch';
let searchEngine = 'kagi';
let torSearchEngine = 'ahmia';
let torEnabled = false;

// Resolve the engine to use right now: the Tor default while the onion toggle
// is on, otherwise the clearnet default.
function activeEngine() {
if (torEnabled) return TOR_SEARCH_ENGINES[torSearchEngine] || TOR_SEARCH_ENGINES.ahmia;
return SEARCH_ENGINES[searchEngine] || SEARCH_ENGINES.neosearch;
return SEARCH_ENGINES[searchEngine] || SEARCH_ENGINES.kagi;
}

function setMode(mode) {
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/extensions/ai-sidebar/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ <h2>bittorrented.com — Live TV, Radio &amp; Podcasts</h2>
<span id="btrMsg" class="saved"></span>

<h2>Search</h2>
<p class="hint">Default search engine for the new-tab search box. NeoSearch is the
private default; Xprivo, DuckDuckGo, and others are available as alternatives. (The omnibox default
<p class="hint">Default search engine for the new-tab search box. Kagi is the
default; NeoSearch, Xprivo, DuckDuckGo, and others are available as alternatives. (The omnibox default
is set separately in <code>chrome://settings/search</code>.)</p>
<label for="searchEngine">New-tab search engine (clearnet)</label>
<select id="searchEngine">
<option value="neosearch">NeoSearch (private, default)</option>
<option value="kagi">Kagi (default)</option>
<option value="neosearch">NeoSearch (private)</option>
<option value="xprivo">Xprivo (private)</option>
<option value="ddg">DuckDuckGo</option>
<option value="altpower">Altpower</option>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/extensions/ai-sidebar/settings-sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function mountSettingsSections({ store, el, flash }) {
const cur = await store.get(['feeds', 'tickers', 'leagues', 'searchEngine', 'torSearchEngine']);

// Populate current values (on every mount, e.g. after a cloud pull).
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'neosearch';
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'kagi';
if (el('torSearchEngine')) el('torSearchEngine').value = cur.torSearchEngine || 'ahmia';
if (el('tickers')) el('tickers').value = cur.tickers ?? '';
if (el('leagues')) el('leagues').value = cur.leagues ?? '';
Expand Down
19 changes: 10 additions & 9 deletions apps/desktop/launcher/tronbrowser
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,10 @@ if flag not in exp:
PY
fi

# Default the omnibox to NeoSearch once per profile, then respect the user's
# chrome://settings/search choice.
if command -v python3 >/dev/null 2>&1 && [ ! -f "$DATA/.tron-search-neosearch" ]; then
# Default the omnibox to Kagi once per profile, then respect the user's
# chrome://settings/search choice. The sentinel filename carries the engine so
# that changing the default here re-applies once on profiles created earlier.
if command -v python3 >/dev/null 2>&1 && [ ! -f "$DATA/.tron-search-kagi" ]; then
TB_PREFS="$DATA/Default/Preferences" python3 - <<'PY' 2>/dev/null || true
import json, os
p = os.environ["TB_PREFS"]
Expand All @@ -198,16 +199,16 @@ try:
except Exception:
d = {}
d.setdefault("default_search_provider_data", {})["template_url_data"] = {
"short_name": "NeoSearch",
"keyword": "neosearch.org",
"url": "https://neosearch.org/?q={searchTerms}",
"suggestions_url": "https://neosearch.org/suggestions?q={searchTerms}",
"favicon_url": "https://neosearch.org/favicon-32x32.png",
"short_name": "Kagi",
"keyword": "kagi.com",
"url": "https://kagi.com/search?q={searchTerms}",
"suggestions_url": "https://kagi.com/api/autosuggest?q={searchTerms}",
"favicon_url": "https://kagi.com/favicon.ico",
"safe_for_autoreplace": False,
}
json.dump(d, open(p, "w"))
PY
mkdir -p "$DATA"; : > "$DATA/.tron-search-neosearch"
mkdir -p "$DATA"; : > "$DATA/.tron-search-kagi"
fi

# Show the feed (ai-sidebar New Tab page) on startup. We can't pass a startup URL —
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/lib/navigation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ describe('normalizeUrl', () => {

it('searches ordinary text', () => {
expect(normalizeUrl('privacy first browser')).toBe(
'https://duckduckgo.com/?q=privacy%20first%20browser',
'https://kagi.com/search?q=privacy%20first%20browser',
);
});

it('searches unsupported schemes instead of loading them', () => {
expect(normalizeUrl('javascript:alert(1)')).toBe(
'https://duckduckgo.com/?q=javascript%3Aalert(1)',
'https://kagi.com/search?q=javascript%3Aalert(1)',
);
});

it('does not treat domain-looking text with spaces as a URL', () => {
expect(normalizeUrl('example.com malicious suffix')).toBe(
'https://duckduckgo.com/?q=example.com%20malicious%20suffix',
'https://kagi.com/search?q=example.com%20malicious%20suffix',
);
});
});
2 changes: 1 addition & 1 deletion apps/mobile/src/lib/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const DOMAIN_OR_IP =
/^(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}|(?:\d{1,3}\.){3}\d{1,3}|localhost)(?::\d{1,5})?(?:[/?#][^\s]*)?$/i;

function searchUrl(query: string): string {
return `https://duckduckgo.com/?q=${encodeURIComponent(query)}`;
return `https://kagi.com/search?q=${encodeURIComponent(query)}`;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion apps/web/public/settings-sections.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export async function mountSettingsSections({ store, el, flash }) {
const cur = await store.get(['feeds', 'tickers', 'leagues', 'searchEngine', 'torSearchEngine']);

// Populate current values (on every mount, e.g. after a cloud pull).
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'neosearch';
if (el('searchEngine')) el('searchEngine').value = cur.searchEngine || 'kagi';
if (el('torSearchEngine')) el('torSearchEngine').value = cur.torSearchEngine || 'ahmia';
if (el('tickers')) el('tickers').value = cur.tickers ?? '';
if (el('leagues')) el('leagues').value = cur.leagues ?? '';
Expand Down
3 changes: 2 additions & 1 deletion apps/web/public/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ <h2>Search</h2>
<p class="hint">Default search engine for the new-tab search box.</p>
<label for="searchEngine">New-tab search engine (clearnet)</label>
<select id="searchEngine">
<option value="neosearch">NeoSearch (private, default)</option>
<option value="kagi">Kagi (default)</option>
<option value="neosearch">NeoSearch (private)</option>
<option value="xprivo">Xprivo (private)</option>
<option value="ddg">DuckDuckGo</option>
<option value="altpower">Altpower</option>
Expand Down
Loading