Skip to content

Commit 11e2bb3

Browse files
committed
0.1.0
Made-with: Cursor
1 parent 206e23e commit 11e2bb3

5 files changed

Lines changed: 71 additions & 13 deletions

File tree

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Get your API key from https://www.bitrefill.com/account/developers
2+
BITREFILL_API_KEY=YOUR_BITREFILL_API_KEY

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
dist/
3+
.env

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,30 @@ The CLI connects to the [Bitrefill MCP server](https://api.bitrefill.com/mcp) an
1212
npm install -g @bitrefill/cli
1313
```
1414

15-
## Usage
15+
## Authentication
16+
17+
### OAuth (default)
18+
19+
On first run, the CLI opens your browser for OAuth authorization. Credentials are stored in `~/.config/bitrefill-cli/`.
20+
21+
### API Key
22+
23+
Generate an API key at [bitrefill.com/account/developers](https://www.bitrefill.com/account/developers) and pass it via the `--api-key` option or the `BITREFILL_API_KEY` environment variable. This skips the OAuth flow entirely.
1624

1725
```bash
18-
bitrefill <command> [options]
26+
# Option
27+
bitrefill --api-key YOUR_API_KEY search-products --query "Netflix"
28+
29+
# Environment variable
30+
export BITREFILL_API_KEY=YOUR_API_KEY
31+
bitrefill search-products --query "Netflix"
1932
```
2033

21-
On first run, the CLI will open your browser for OAuth authorization. Credentials are stored in `~/.config/bitrefill-cli/`.
34+
## Usage
35+
36+
```bash
37+
bitrefill [--api-key <key>] <command> [options]
38+
```
2239

2340
### Examples
2441

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bitrefill/cli",
3-
"version": "0.0.3",
3+
"version": "0.1.0",
44
"description": "Bitrefill - browse, buy, and manage gift cards, mobile top-ups, and eSIMs",
55
"type": "module",
66
"bin": {

src/index.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,25 @@ import path from 'node:path';
2424
import os from 'node:os';
2525
import { execSync } from 'node:child_process';
2626

27-
const DEFAULT_MCP_URL = process.env.MCP_URL || 'https://api.bitrefill.com/mcp';
27+
const BASE_MCP_URL = 'https://api.bitrefill.com/mcp';
2828
const CALLBACK_PORT = 8098;
2929
const CALLBACK_URL = `http://127.0.0.1:${CALLBACK_PORT}/callback`;
3030
const STATE_DIR = path.join(os.homedir(), '.config', 'bitrefill-cli');
3131

32+
function resolveApiKey(): string | undefined {
33+
const idx = process.argv.indexOf('--api-key');
34+
if (idx !== -1 && idx + 1 < process.argv.length) {
35+
return process.argv[idx + 1];
36+
}
37+
return process.env.BITREFILL_API_KEY;
38+
}
39+
40+
function resolveMcpUrl(apiKey?: string): string {
41+
if (process.env.MCP_URL) return process.env.MCP_URL;
42+
if (apiKey) return `${BASE_MCP_URL}/${apiKey}`;
43+
return BASE_MCP_URL;
44+
}
45+
3246
// --- Persistent OAuth state ---
3347

3448
interface PersistedState {
@@ -171,18 +185,28 @@ function waitForCallback(): Promise<string> {
171185
// --- MCP connection ---
172186

173187
async function createMcpClient(
174-
url: string
188+
url: string,
189+
useOAuth: boolean
175190
): Promise<{ client: Client; transport: StreamableHTTPClientTransport }> {
176-
const authProvider = createOAuthProvider(url);
177-
178191
const suppressNoise = (err: Error) => {
179192
if (err instanceof UnauthorizedError) return;
180193
if (err.message?.includes('SSE stream disconnected')) return;
194+
if (err.message?.includes('Failed to open SSE stream')) return;
181195
console.error('Client error:', err);
182196
};
183197

198+
if (!useOAuth) {
199+
const c = new Client({ name: 'bitrefill-cli', version: '0.1.0' });
200+
c.onerror = suppressNoise;
201+
const t = new StreamableHTTPClientTransport(new URL(url));
202+
await c.connect(t);
203+
return { client: c, transport: t };
204+
}
205+
206+
const authProvider = createOAuthProvider(url);
207+
184208
const tryConnect = async () => {
185-
const c = new Client({ name: 'bitrefill-cli', version: '0.0.1' });
209+
const c = new Client({ name: 'bitrefill-cli', version: '0.1.0' });
186210
c.onerror = suppressNoise;
187211
const t = new StreamableHTTPClientTransport(new URL(url), {
188212
authProvider,
@@ -200,7 +224,7 @@ async function createMcpClient(
200224
const code = await waitForCallback();
201225
console.log('Authorization code received.');
202226

203-
const c = new Client({ name: 'bitrefill-cli', version: '0.0.1' });
227+
const c = new Client({ name: 'bitrefill-cli', version: '0.1.0' });
204228
c.onerror = suppressNoise;
205229
const t = new StreamableHTTPClientTransport(new URL(url), {
206230
authProvider,
@@ -311,8 +335,12 @@ function optionKey(s: string): string {
311335
// --- Main ---
312336

313337
async function main(): Promise<void> {
338+
const apiKey = resolveApiKey();
339+
const mcpUrl = resolveMcpUrl(apiKey);
340+
const useOAuth = !apiKey && !process.env.MCP_URL;
341+
314342
// Phase 1: connect and discover tools
315-
const { client, transport } = await createMcpClient(DEFAULT_MCP_URL);
343+
const { client, transport } = await createMcpClient(mcpUrl, useOAuth);
316344

317345
const toolsResult = await client.request(
318346
{ method: 'tools/list', params: {} },
@@ -326,14 +354,24 @@ async function main(): Promise<void> {
326354
.description(
327355
'Bitrefill CLI - browse, buy, and manage gift cards, mobile top-ups, and eSIMs.\n\nTerms: https://www.bitrefill.com/terms\nPrivacy: https://www.bitrefill.com/privacy'
328356
)
329-
.version('0.0.1');
357+
.version('0.1.0')
358+
.option(
359+
'--api-key <key>',
360+
'Bitrefill API key (overrides BITREFILL_API_KEY env var)'
361+
);
330362

331363
program
332364
.command('logout')
333365
.description('Clear stored OAuth credentials')
334366
.action(() => {
367+
if (!useOAuth) {
368+
console.log(
369+
'Using API key authentication — no stored credentials to clear.'
370+
);
371+
return;
372+
}
335373
try {
336-
fs.unlinkSync(stateFilePath(DEFAULT_MCP_URL));
374+
fs.unlinkSync(stateFilePath(mcpUrl));
337375
console.log('Cleared stored credentials.');
338376
} catch {
339377
console.log('No stored credentials to clear.');

0 commit comments

Comments
 (0)