Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
aeac4ea
Initial plan
Claude Mar 6, 2026
706b492
Add Ramclouds API provider
Claude Mar 6, 2026
8966c72
Merge branch 'master' into claude/add-ramclouds-api-provider
fdkgenie Mar 6, 2026
356d040
Merge pull request #1 from fdkgenie/claude/add-ramclouds-api-provider
fdkgenie Mar 6, 2026
fd4aa6f
Fix auto-fetch models for Ramclouds provider
fdkgenie Mar 6, 2026
65e901b
Add Amp CLI support to CLI Tools configuration
fdkgenie Mar 6, 2026
73d3dfb
Implement full server-side proxy logic for Amp CLI support
fdkgenie Mar 6, 2026
df15fdb
Fix Amp CLI auth and proxy compatibility across management routes.
fdkgenie Mar 6, 2026
691bec3
Merge pull request #2 from fdkgenie/dev_ramclouds
fdkgenie Mar 6, 2026
d044536
Add dynamic model fetching support for providers
fdkgenie Mar 6, 2026
4e68536
Merge pull request #3 from fdkgenie/dev_ramclouds
fdkgenie Mar 6, 2026
435c2cd
Fix streaming/non-streaming proxy response framing
fdkgenie Mar 6, 2026
ec34ccc
Add JSON-to-SSE conversion for streaming responses
fdkgenie Mar 7, 2026
196755d
feat(dashboard): add playground navigation and route
fdkgenie Mar 7, 2026
7df1fdb
feat(playground): add simple model testing UI
fdkgenie Mar 7, 2026
ae21103
chore(playground): polish auth input and request handling
fdkgenie Mar 7, 2026
254b4ba
feat(playground): add high-priority templates, streaming, history, an…
fdkgenie Mar 7, 2026
7ba6e3f
feat(playground): add medium-priority features - params and chat session
fdkgenie Mar 7, 2026
7b6dde7
feat(playground): add advanced features - debug panel, cost estimatio…
fdkgenie Mar 7, 2026
75d8f52
refactor(playground): improve layout with 2-column responsive design
fdkgenie Mar 7, 2026
b2777e8
Merge pull request #4 from fdkgenie/dev_playground
fdkgenie Mar 7, 2026
c62a723
feat: convert to npm CLI package 9router-fdk
fdkgenie Mar 7, 2026
bf42501
feat: add Ramclouds quota tracker support
fdkgenie Mar 7, 2026
d8a8f01
feat: support API key providers in quota tracker UI
fdkgenie Mar 7, 2026
fd30c80
fix: allow API key providers to fetch usage data
fdkgenie Mar 7, 2026
57e8bda
fix: support both accessToken and apiKey fields in usage API
fdkgenie Mar 8, 2026
5e31f6e
Merge pull request #5 from fdkgenie/dev_quota
fdkgenie Mar 8, 2026
d89a92c
chore: bump version to 0.3.33-b
fdkgenie Mar 8, 2026
ae81422
Initial plan
Claude Mar 8, 2026
ad5bbb1
feat: Add system tray with enhanced information display
Claude Mar 8, 2026
3ecce99
feat: Add tray implementation files and documentation
Claude Mar 8, 2026
a850fcf
docs: Add system tray section to main README
Claude Mar 8, 2026
64649bb
docs: Add comprehensive testing guide for system tray
Claude Mar 8, 2026
e270a64
fix: Add error handling fallbacks for icon and dashboard
Claude Mar 8, 2026
f0c8f4d
docs: Add comprehensive implementation summary
Claude Mar 8, 2026
3f02240
Merge pull request #8 from fdkgenie/claude/add-additional-info-to-tray
fdkgenie Mar 8, 2026
33a477a
feat: Add USD quota display and reset time for Ramclouds
fdkgenie Mar 9, 2026
d5367e0
refactor: finalize systray-only tray architecture and remove Electron…
fdkgenie Mar 9, 2026
45710c5
fix: include CLI tray sources in npm package for --tray mode
fdkgenie Mar 9, 2026
a9bda2a
Merge pull request #9 from fdkgenie/dev_main
fdkgenie Mar 9, 2026
988e80a
fix(mitm): fix Copilot /responses API - route, output array, [DONE] s…
fdkgenie Mar 12, 2026
b38f39c
docs: Amp CLI configuration guide (#12)
fdkgenie Mar 12, 2026
763c6f1
cleanup
fdkgenie Mar 12, 2026
3e7bd97
fix: support standalone build and convert CLI to CommonJS
fdkgenie Mar 12, 2026
659d7eb
chore: bump version to 0.3.41
fdkgenie Mar 12, 2026
f94286a
fix: move require() calls to top level to fix ES module error
fdkgenie Mar 12, 2026
da15563
chore: optimize npm package size
fdkgenie Mar 12, 2026
aab8c89
0.3.42
fdkgenie Mar 12, 2026
cfe4f13
fix: include .next/standalone build in npm package
fdkgenie Mar 12, 2026
09b8b75
0.3.43
fdkgenie Mar 12, 2026
dddd033
feat: auto-fetch models after OAuth connection creation
fdkgenie Mar 16, 2026
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ source/*
.cursor/*
docs/*
!docs/ARCHITECTURE.md
!docs/AMP_CLI_IMPLEMENTATION_REPORT.md
test/*
bin/*
open-sse/test/*
RM.vn.md
RM.md
Expand Down
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ data/

# Development
src/
!src/cli/
docs/
test/
agents/
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,25 @@ npm run build
PORT=20128 HOSTNAME=0.0.0.0 NEXT_PUBLIC_BASE_URL=http://localhost:20128 npm run start
```

**System Tray Mode (Desktop App):**

Run 9Router with a system tray icon for easy access:

```bash
npm run build
npm run start:tray
```

The system tray provides:
- Quick access to dashboard
- Real-time model and usage information
- Context statistics (input/output/total tokens)
- Quota tracker for all providers
- MITM server toggle
- Autostart option

See [bin/README.md](bin/README.md) for more details.

Default URLs:
- Dashboard: `http://localhost:20128/dashboard`
- OpenAI-compatible API: `http://localhost:20128/v1`
Expand Down
236 changes: 236 additions & 0 deletions bin/cli-menu.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
#!/usr/bin/env node

const { spawn } = require('child_process');
const path = require('path');
const readline = require('readline');

// Configuration
const PORT = process.env.PORT || 20127;
const HOSTNAME = process.env.HOSTNAME || '0.0.0.0';

let serverProcess = null;

/**
* Start the Next.js server
*/
function startServer() {
return new Promise((resolve, reject) => {
const serverPath = path.join(__dirname, '..', '.next', 'standalone', 'server.js');

// Check if built server exists
const fs = require('fs');
if (!fs.existsSync(serverPath)) {
console.error('Server not built. Please run: npm run build');
reject(new Error('Server not built'));
return;
}

console.log('Starting 9Router server...');

serverProcess = spawn('node', [serverPath], {
env: {
...process.env,
PORT,
HOSTNAME,
NODE_ENV: 'production'
},
stdio: 'inherit'
});

serverProcess.on('error', (err) => {
console.error('Failed to start server:', err);
reject(err);
});

// Wait for server to be ready
setTimeout(() => {
console.log('\n✓ Server started successfully');
console.log(`✓ Dashboard: http://localhost:${PORT}/dashboard\n`);
resolve();
}, 3000);
});
}

/**
* Stop the Next.js server
*/
function stopServer() {
if (serverProcess) {
console.log('\nStopping server...');
serverProcess.kill();
serverProcess = null;
}
}

/**
* Display menu
*/
function displayMenu() {
console.log('\n╔════════════════════════════════════════╗');
console.log('║ 9Router Control Menu ║');
console.log('╠════════════════════════════════════════╣');
console.log('║ [1] Open Dashboard in Browser ║');
console.log('║ [2] Show Server Status ║');
console.log('║ [3] Restart Server ║');
console.log('║ [4] View Logs ║');
console.log('║ [q] Quit ║');
console.log('╚════════════════════════════════════════╝\n');
process.stdout.write('Select an option: ');
}

/**
* Open dashboard in browser
*/
function openDashboard() {
const open = require('child_process').exec;
const url = `http://localhost:${PORT}/dashboard`;

const command = process.platform === 'darwin' ? 'open' :
process.platform === 'win32' ? 'start' : 'xdg-open';

open(`${command} ${url}`, (error) => {
if (error) {
console.log(`\n✗ Failed to open browser. Please visit: ${url}\n`);
} else {
console.log(`\n✓ Opening dashboard in browser...\n`);
}
});
}

/**
* Show server status
*/
function showStatus() {
const http = require('http');

const options = {
hostname: 'localhost',
port: PORT,
path: '/api/health',
method: 'GET',
timeout: 2000
};

const req = http.request(options, (res) => {
console.log(`\n✓ Server Status: Running`);
console.log(`✓ Port: ${PORT}`);
console.log(`✓ URL: http://localhost:${PORT}/dashboard\n`);
});

req.on('error', () => {
console.log(`\n✗ Server Status: Not responding\n`);
});

req.on('timeout', () => {
console.log(`\n✗ Server Status: Timeout\n`);
req.destroy();
});

req.end();
}

/**
* Restart server
*/
async function restartServer() {
console.log('\nRestarting server...');
stopServer();
await new Promise(resolve => setTimeout(resolve, 2000));
await startServer();
}

/**
* View logs
*/
function viewLogs() {
console.log('\n[Logs are displayed in the main console output]\n');
}

/**
* Handle menu input
*/
function handleInput(input, rl) {
const choice = input.trim().toLowerCase();

switch (choice) {
case '1':
openDashboard();
displayMenu();
break;
case '2':
showStatus();
displayMenu();
break;
case '3':
restartServer().then(() => displayMenu());
break;
case '4':
viewLogs();
displayMenu();
break;
case 'q':
case 'quit':
case 'exit':
console.log('\nShutting down 9Router...');
stopServer();
rl.close();
process.exit(0);
break;
default:
console.log('\n✗ Invalid option. Please try again.\n');
displayMenu();
break;
}
}

/**
* Initialize the CLI
*/
async function initialize() {
try {
// Start the server
await startServer();

// Create readline interface
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// Display initial menu
displayMenu();

// Handle user input
rl.on('line', (input) => {
handleInput(input, rl);
});

// Handle Ctrl+C
rl.on('SIGINT', () => {
console.log('\n\nReceived SIGINT. Shutting down...');
stopServer();
rl.close();
process.exit(0);
});

} catch (error) {
console.error('Failed to initialize:', error);
process.exit(1);
}
}

// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error);
stopServer();
process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled rejection at:', promise, 'reason:', reason);
stopServer();
process.exit(1);
});

// Start the CLI
initialize();
Loading