Skip to content
Draft
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
3 changes: 3 additions & 0 deletions new/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
*.log
coverage/
102 changes: 102 additions & 0 deletions new/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# GitHub Omnibox — v2

A completely rewritten Chrome extension that lets you navigate GitHub directly
from the address bar using the same command syntax as the **[GitHub CLI (`gh`)](https://cli.github.com/manual/)**.

No login, no API token, no cache — suggestions are sourced from your **browser
history** and GitHub's **public Search API**.

---

## Usage

Type `gh` followed by `Space` or `Tab` in Chrome's address bar to activate.

```
gh <command> <subcommand> [args...]
```

| Command | Example | Navigates to |
|---------|---------|--------------|
| `repo view` | `repo view facebook/react` | `github.com/facebook/react` |
| `repo list` | `repo list microsoft` | `github.com/microsoft` |
| `repo create` | `repo create` | `github.com/new` |
| `repo fork` | `repo fork facebook/react` | `github.com/facebook/react/fork` |
| `repo rename` | `repo rename facebook/react` | `github.com/facebook/react/settings` |
| `issue list` | `issue list facebook/react` | `github.com/facebook/react/issues` |
| `issue view` | `issue view facebook/react 42` | `github.com/facebook/react/issues/42` |
| `issue create` | `issue create facebook/react` | `github.com/facebook/react/issues/new` |
| `pr list` | `pr list facebook/react` | `github.com/facebook/react/pulls` |
| `pr view` | `pr view facebook/react 7` | `github.com/facebook/react/pull/7` |
| `pr create` | `pr create facebook/react` | `github.com/facebook/react/compare` |
| `release list` | `release list facebook/react` | `github.com/facebook/react/releases` |
| `release view` | `release view facebook/react v18.0.0` | `github.com/facebook/react/releases/tag/v18.0.0` |
| `release create` | `release create facebook/react` | `github.com/facebook/react/releases/new` |
| `gist list` | `gist list` | `gist.github.com` |
| `gist view` | `gist view abc123` | `gist.github.com/abc123` |
| `gist create` | `gist create` | `gist.github.com` |
| `workflow list` | `workflow list facebook/react` | `github.com/facebook/react/actions` |
| `run list` | `run list facebook/react` | `github.com/facebook/react/actions` |
| `search repos` | `search repos react hooks` | `github.com/search?q=react+hooks&type=repositories` |
| `search issues` | `search issues memory leak` | `github.com/search?q=memory+leak&type=issues` |
| `search prs` | `search prs chore deps` | `github.com/search?q=chore+deps&type=pullrequests` |
| `search code` | `search code useState` | `github.com/search?q=useState&type=code` |

**Shorthand**: if you type `owner/repo` directly (no command), it navigates
straight to `github.com/owner/repo`.

---

## Architecture

```
new/
├── manifest.json Chrome MV3 manifest (keyword: gh, no OAuth)
├── background.js Service worker — wires omnibox events
├── src/
│ ├── commands.js gh CLI–aligned command tree + URL builders (pure)
│ ├── parser.js Input tokeniser (pure)
│ ├── suggest.js Suggestion engine — commands + history + search (pure)
│ ├── navigate.js URL resolver for onInputEntered (pure)
│ ├── history.js Browser-history wrapper (injectable dep)
│ └── search.js GitHub Search API wrapper (injectable dep, no auth)
└── test/
├── commands.test.js
├── parser.test.js
├── suggest.test.js
├── navigate.test.js
├── history.test.js
└── search.test.js
```

### Design principles

* **Pure functions everywhere** — `commands.js`, `parser.js`, `suggest.js`,
and `navigate.js` have zero side-effects and are trivially unit-testable.
* **Injectable dependencies** — `history.js` and `search.js` accept the
`chrome.history.search` / `fetch` function as a parameter, making them
mockable without a browser.
* **No caching, no auth** — suggestions come from browser history (instant,
free) and GitHub's public Search API (no token needed).
* **Manifest V3** — modern service-worker–based background script.

---

## Installation (development)

1. Copy the `new/` folder to your machine.
2. Copy the icon files from the parent `images/` directory into `new/images/`.
3. Open `chrome://extensions`, enable **Developer mode**.
4. Click **Load unpacked** and select the `new/` folder.

---

## Running tests

```bash
cd new/
npm install
npm test
```

Requires Node.js ≥ 18.
100 changes: 100 additions & 0 deletions new/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* GitHub Omnibox — service worker (Manifest V3)
*
* Handles Chrome Omnibox events for the "gh" keyword.
* Suggestions are powered by:
* 1. Static gh-CLI–compatible command tree
* 2. Browser history (no API key required)
* 3. GitHub public Search API (no auth required)
*
* GitHub Enterprise support:
* Right-click the extension icon and choose "Set as GitHub Enterprise domain"
* while on your GHE instance. The setting is stored in chrome.storage.sync
* and applied to all URL generation and history searches automatically.
* Choose "Reset to github.com" to revert to the public host.
*/

import { DEFAULT_BASE, makeCommands } from './src/commands.js';
import { getSuggestions } from './src/suggest.js';
import { searchHistory } from './src/history.js';
import { searchGitHub } from './src/search.js';
import { resolve } from './src/navigate.js';

// ── GHE settings helpers ────────────────────────────────────────────────────

/** @returns {Promise<string>} stored GHE base URL or the default github.com base */
function getBase() {
return new Promise(res => {
chrome.storage.sync.get('gheBase', ({ gheBase }) => res(gheBase || DEFAULT_BASE));
});
}

// ── Extension lifecycle ─────────────────────────────────────────────────────

chrome.runtime.onInstalled.addListener(() => {
// Right-click the extension icon → context menu items for GHE configuration
chrome.contextMenus.create({
id: 'set-ghe-domain',
title: 'Set as GitHub Enterprise domain',
contexts: ['action'],
});
chrome.contextMenus.create({
id: 'reset-to-github',
title: 'Reset to github.com',
contexts: ['action'],
});
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'set-ghe-domain' && tab?.url) {
try {
const { protocol, hostname } = new URL(tab.url);
const base = `${protocol}//${hostname}`;
chrome.storage.sync.set({ gheBase: base });
} catch { /* ignore unparseable URLs */ }
} else if (info.menuItemId === 'reset-to-github') {
chrome.storage.sync.remove('gheBase');
}
});

// ── Omnibox ─────────────────────────────────────────────────────────────────

chrome.omnibox.setDefaultSuggestion({
description: 'GitHub: type <match>owner/repo</match> to open a repo, or a command — <dim>repo view</dim>, <dim>issue list</dim>, <dim>pr create</dim>',
});

chrome.omnibox.onInputChanged.addListener(async (text, suggest) => {
const base = await getBase();
const commands = makeCommands(base);

// Derive the current partial token for online/history search
const lastToken = text.trim().split(/\s+/).pop() ?? '';

const [historyRepos, searchRepos] = await Promise.all([
searchHistory(lastToken, chrome.history.search.bind(chrome.history), base),
searchGitHub(lastToken),
]);

const suggestions = getSuggestions({ text, commands, historyRepos, searchRepos });
if (suggestions.length > 0) suggest(suggestions);
});

chrome.omnibox.onInputEntered.addListener(async (text, disposition) => {
const base = await getBase();
const commands = makeCommands(base);
const url = resolve(text, commands, base);

switch (disposition) {
case 'currentTab':
chrome.tabs.update({ url });
break;
case 'newForegroundTab':
chrome.tabs.create({ url });
break;
case 'newBackgroundTab':
chrome.tabs.create({ url, active: false });
break;
default:
chrome.tabs.update({ url });
}
});
30 changes: 30 additions & 0 deletions new/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"manifest_version": 3,
"name": "GitHub Omnibox",
"short_name": "gh",
"description": "Navigate GitHub from the address bar using gh CLI commands (no login required)",
"version": "2.0.0",
"homepage_url": "https://github.com/ProLoser/Github-Omnibox",
"omnibox": { "keyword": "gh" },
"action": {
"default_icon": {
"16": "images/favicon-16.png",
"32": "images/favicon-32.png",
"128": "images/fluidicon.png"
},
"default_title": "GitHub Omnibox"
},
"permissions": ["history", "tabs", "contextMenus", "storage"],
"host_permissions": [
"https://api.github.com/*"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"icons": {
"16": "images/favicon-16.png",
"32": "images/favicon-32.png",
"128": "images/fluidicon.png"
}
}
Loading