Skip to content

Commit ef6f329

Browse files
committed
fix: Ctrl+C notification only, auto-port, help version
Fix: Ctrl+C - Ctrl+C shows red bold status notification (never quit panel) - Quit only via [q] key Fix: auto-increment port - If configured port is in use, bridge tries port+1..+100 - Port switch logged to log sidebar - TUI header shows actual running port (currentPort) Fix: help text - -v / --version flag listed in help screen Fix: port references - AppConfig.defaultPort constant replaces all hardcoded 17077 - Port config panel placeholder/hint/reset use dynamic default See CHANGELOG.md for full details.
1 parent 7fbf5b5 commit ef6f329

10 files changed

Lines changed: 60 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## v1.1.1 (2026-07-29)
6+
7+
### Fixes
8+
9+
- Ctrl+C: status bar notification only (no quit panel). Use [q] to quit.
10+
- Auto-increment port: if configured port is in use, bridge tries port+1, +2, ... up to 100 attempts, logs the switch
11+
- Help screen now includes `-v` / `--version` flag
12+
513
## v1.1.0 (2026-07-28)
614

715
### Features

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ APIs for use with OpenCode, Zed, Cursor, or any compatible client.
2121
## Quick Install
2222

2323
```
24-
npm install -g ./commandcode-bridge-v1.1.0.tgz
24+
npm install -g ./commandcode-bridge-v1.1.1.tgz
2525
commandcode-bridge run
2626
```
2727

docs/TUI.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
| `p` | Always | Configure proxy port (with availability scan) |
2323
| `h` | Always | Open help |
2424
| `q` | Always | Quit (with confirmation) |
25+
| `Ctrl+C` | Always | Status bar hint (use [q] to quit) |
2526
| `up/down` | Main | Scroll content / navigate models |
2627
| `PgUp/PgDn` | Main | Scroll 10 lines |
2728
| `Enter` | Models page | Copy selected model codename to clipboard |
@@ -47,6 +48,10 @@ Press `[p]` to open port config panel:
4748
- Config saved to `~/.config/commandcode-bridge/config.json`
4849
- Restart required for change to take effect
4950

51+
If the configured port is in use at startup, the bridge auto-increments
52+
(port+1, port+2, ...) up to 100 attempts until it finds a free port.
53+
The switch is logged and the running port is shown in the header.
54+
5055
## Plan Access
5156

5257
Models in page 5 are prioritized by your plan:

lib/src/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Commands:
2121
run --server Start the bridge in headless server mode
2222
update Download and install latest stable release
2323
help Show this help screen
24+
-v, --version Print version string
2425
''';
2526

2627
void _printUsage() => stdout.write(_usage);

lib/src/models/account.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,21 +76,23 @@ class AccountStore {
7676
}
7777

7878
class AppConfig {
79+
static const defaultPort = 17077;
80+
7981
int serverPort;
8082
String apiBaseUrl;
8183
String cliVersion;
8284

8385
// Port 17077 chosen to avoid neighbors with cobuddy-bridge (20130)
8486
// and common service ports
8587
AppConfig({
86-
this.serverPort = 17077,
88+
this.serverPort = defaultPort,
8789
this.apiBaseUrl = 'https://api.commandcode.ai',
8890
this.cliVersion = '1.4.1',
8991
});
9092

9193
factory AppConfig.fromJson(Map<String, dynamic> json) {
9294
return AppConfig(
93-
serverPort: json['server_port'] as int? ?? 17077,
95+
serverPort: json['server_port'] as int? ?? defaultPort,
9496
apiBaseUrl: json['api_base_url'] as String? ?? 'https://api.commandcode.ai',
9597
cliVersion: json['cli_version'] as String? ?? '1.4.1',
9698
);

lib/src/models/version.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
const bridgeVersion = '1.1.0';
1+
const bridgeVersion = '1.1.1';

lib/src/server/server_controller.dart

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,39 @@ class ServerController {
1818
bool get isRunning => _running;
1919
String get currentModel => _currentModel;
2020
int get modelVersion => _modelVersion;
21+
int get currentPort => _runningPort;
2122

2223
ServerController({required this.accountStore, required this.configStore});
2324

25+
int _runningPort = 0;
26+
2427
Future<void> start() async {
2528
if (_running) return;
26-
try {
27-
_server = await HttpServer.bind(
28-
InternetAddress.loopbackIPv4,
29-
configStore.config.serverPort,
30-
);
31-
_running = true;
32-
LogStore.success('Bridge started on port ${configStore.config.serverPort}');
33-
_server!.listen(_handleRequest);
34-
} catch (e) {
35-
LogStore.error('Failed to start server: $e');
36-
rethrow;
29+
30+
var port = configStore.config.serverPort;
31+
const maxAttempts = 100;
32+
33+
for (var i = 0; i < maxAttempts; i++) {
34+
try {
35+
_server = await HttpServer.bind(
36+
InternetAddress.loopbackIPv4,
37+
port,
38+
);
39+
if (port != configStore.config.serverPort) {
40+
LogStore.warning('Port ${configStore.config.serverPort} in use, auto-incremented to $port');
41+
}
42+
_runningPort = port;
43+
_running = true;
44+
LogStore.success('Bridge started on port $port');
45+
_server!.listen(_handleRequest);
46+
return;
47+
} catch (e) {
48+
LogStore.warning('Port $port in use, trying ${port + 1}...');
49+
port++;
50+
}
3751
}
52+
53+
throw Exception('No available port found after $maxAttempts attempts');
3854
}
3955

4056
void stop() {

lib/src/tui/app.dart

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class AppState extends State<CmdBridgeApp> {
154154
final msg = _status;
155155
if (msg.startsWith('Data refreshed') || msg.contains('Copied')) return Colors.green;
156156
if (msg.startsWith('Fetching') || msg.startsWith('Opening') || msg.startsWith('Copying')) return Colors.cyan;
157+
if (msg.startsWith('Use [q] to quit')) return Colors.red;
157158
if (msg.startsWith('No API') || msg.startsWith('Refresh failed') || msg.contains('failed') || msg.contains('Invalid') || msg.contains('in use') || msg.contains('Cannot')) return Colors.red;
158159
if (msg.contains('Clear') || msg.contains('Warning') || msg.contains('Restart') || msg.contains('Already') || msg.contains('confirm') || msg.startsWith('No entries')) return Colors.yellow;
159160
return Colors.cyan;
@@ -249,9 +250,7 @@ class AppState extends State<CmdBridgeApp> {
249250

250251
if (_panel == _Panel.main) {
251252
if (e.logicalKey == LogicalKey.keyC && e.isControlPressed) {
252-
_panel = _Panel.quit;
253-
_setStatus('Press q or Ctrl+C to quit', duration: 10);
254-
setState(() {});
253+
_setStatus('Use [q] to quit', duration: 3);
255254
return true;
256255
}
257256

@@ -614,9 +613,9 @@ class AppState extends State<CmdBridgeApp> {
614613
void _doSetPort() {
615614
final raw = _portCtrl.text.trim();
616615
if (raw.isEmpty) {
617-
_config.config.serverPort = 17077;
616+
_config.config.serverPort = AppConfig.defaultPort;
618617
_config.save();
619-
_setStatus('Port reset to default 17077. Restart required.', duration: 5);
618+
_setStatus('Port reset to default ${AppConfig.defaultPort}. Restart required.', duration: 5);
620619
_panel = _Panel.main;
621620
setState(() {});
622621
return;
@@ -658,7 +657,7 @@ class AppState extends State<CmdBridgeApp> {
658657
final email = whoami?.email ?? '';
659658
final planName = sub != null ? _planDisplayName(sub.planId) : '';
660659
final isRunning = _proxy.isRunning;
661-
final port = _config.config.serverPort;
660+
final port = _proxy.isRunning ? _proxy.currentPort : _config.config.serverPort;
662661

663662
return Container(
664663
padding: const EdgeInsets.all(1),
@@ -1474,7 +1473,7 @@ class AppState extends State<CmdBridgeApp> {
14741473
child: TextField(
14751474
controller: _portCtrl,
14761475
focused: true,
1477-
placeholder: '17077',
1476+
placeholder: '${AppConfig.defaultPort}',
14781477
onSubmitted: (_) => _doSetPort(),
14791478
),
14801479
),
@@ -1500,7 +1499,7 @@ class AppState extends State<CmdBridgeApp> {
15001499
const Text('No recommended ports available',
15011500
style: TextStyle(color: Colors.red)),
15021501
const SizedBox(height: 1),
1503-
const Text('Empty = reset to default (17077).',
1502+
Text('Empty = reset to default (${AppConfig.defaultPort}).',
15041503
style: TextStyle(color: Colors.grey)),
15051504
const SizedBox(height: 1),
15061505
const Text('Restart required for port change.',
@@ -1529,7 +1528,10 @@ class AppState extends State<CmdBridgeApp> {
15291528
),
15301529
child: Row(
15311530
children: [
1532-
Text(_status, style: TextStyle(color: _notifColor())),
1531+
Text(_status, style: TextStyle(
1532+
color: _notifColor(),
1533+
fontWeight: _status.startsWith('Use [q] to quit') ? FontWeight.bold : null,
1534+
)),
15331535
],
15341536
),
15351537
);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "commandcode-bridge",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "Command Code single-account proxy bridge with TUI dashboard. OpenAI-compatible endpoint for OpenCode, Cursor, etc.",
55
"type": "module",
66
"bin": {

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: commandcode_bridge
22
description: Command Code single-account proxy bridge with OpenAI-compatible endpoint
3-
version: 1.1.0
3+
version: 1.1.1
44

55
environment:
66
sdk: ^3.10.8

0 commit comments

Comments
 (0)