@ai-ide-bridge/cli 1.0.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/commands/configure.js +78 -10
  3. package/dist/commands/daemon.d.ts +1 -0
  4. package/dist/commands/daemon.js +107 -13
  5. package/dist/commands/doctor.js +1 -1
  6. package/dist/commands/init.js +70 -5
  7. package/dist/commands/login.d.ts +1 -0
  8. package/dist/commands/login.js +62 -0
  9. package/dist/commands/logout.d.ts +1 -0
  10. package/dist/commands/logout.js +12 -0
  11. package/dist/commands/start.js +4 -4
  12. package/dist/core/config.d.ts +4 -0
  13. package/dist/core/config.js +43 -0
  14. package/dist/core/daemon-session.d.ts +14 -0
  15. package/dist/core/daemon-session.js +179 -0
  16. package/dist/core/daemon.d.ts +16 -0
  17. package/dist/core/daemon.js +168 -0
  18. package/dist/core/formatter.d.ts +3 -0
  19. package/dist/core/formatter.js +44 -0
  20. package/dist/core/index.d.ts +9 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/core/parser.d.ts +164 -0
  23. package/dist/core/parser.js +37 -0
  24. package/dist/core/registry.d.ts +16 -0
  25. package/dist/core/registry.js +53 -0
  26. package/dist/core/server.d.ts +19 -0
  27. package/dist/core/server.js +185 -0
  28. package/dist/core/session.d.ts +11 -0
  29. package/dist/core/session.js +39 -0
  30. package/dist/core/types.d.ts +166 -0
  31. package/dist/core/types.js +44 -0
  32. package/dist/index.js +22 -5
  33. package/dist/oauth/device-flow.d.ts +12 -0
  34. package/dist/oauth/device-flow.js +93 -0
  35. package/dist/oauth/flow.d.ts +11 -0
  36. package/dist/oauth/flow.js +75 -0
  37. package/dist/oauth/index.d.ts +6 -0
  38. package/dist/oauth/index.js +5 -0
  39. package/dist/oauth/lifecycle.d.ts +13 -0
  40. package/dist/oauth/lifecycle.js +56 -0
  41. package/dist/oauth/providers.d.ts +2 -0
  42. package/dist/oauth/providers.js +19 -0
  43. package/dist/oauth/storage-file.d.ts +2 -0
  44. package/dist/oauth/storage-file.js +68 -0
  45. package/dist/oauth/storage.d.ts +2 -0
  46. package/dist/oauth/storage.js +4 -0
  47. package/dist/oauth/types.d.ts +44 -0
  48. package/dist/oauth/types.js +1 -0
  49. package/dist/plugins/copilot/auth.d.ts +7 -0
  50. package/dist/plugins/copilot/auth.js +30 -0
  51. package/dist/plugins/copilot/index.d.ts +5 -0
  52. package/dist/plugins/copilot/index.js +4 -0
  53. package/dist/plugins/copilot/plugin.d.ts +8 -0
  54. package/dist/plugins/copilot/plugin.js +29 -0
  55. package/dist/plugins/copilot/session.d.ts +8 -0
  56. package/dist/plugins/copilot/session.js +115 -0
  57. package/dist/plugins/copilot/tools.d.ts +10 -0
  58. package/dist/plugins/copilot/tools.js +10 -0
  59. package/dist/plugins/copilot/types.d.ts +15 -0
  60. package/dist/plugins/copilot/types.js +27 -0
  61. package/dist/plugins/cursor/index.d.ts +2 -0
  62. package/dist/plugins/cursor/index.js +2 -0
  63. package/dist/plugins/cursor/plugin.d.ts +8 -0
  64. package/dist/plugins/cursor/plugin.js +36 -0
  65. package/dist/plugins/cursor/session.d.ts +11 -0
  66. package/dist/plugins/cursor/session.js +69 -0
  67. package/dist/plugins/cursor/tools.d.ts +11 -0
  68. package/dist/plugins/cursor/tools.js +13 -0
  69. package/dist/plugins/windsurf/auth.d.ts +3 -0
  70. package/dist/plugins/windsurf/auth.js +20 -0
  71. package/dist/plugins/windsurf/daemon.d.ts +6 -0
  72. package/dist/plugins/windsurf/daemon.js +16 -0
  73. package/dist/plugins/windsurf/index.d.ts +5 -0
  74. package/dist/plugins/windsurf/index.js +4 -0
  75. package/dist/plugins/windsurf/models.d.ts +2 -0
  76. package/dist/plugins/windsurf/models.js +42 -0
  77. package/dist/plugins/windsurf/plugin.d.ts +8 -0
  78. package/dist/plugins/windsurf/plugin.js +31 -0
  79. package/dist/plugins/windsurf/session.d.ts +5 -0
  80. package/dist/plugins/windsurf/session.js +6 -0
  81. package/dist/plugins/windsurf/tools.d.ts +3 -0
  82. package/dist/plugins/windsurf/tools.js +10 -0
  83. package/dist/plugins/windsurf/types.d.ts +22 -0
  84. package/dist/plugins/windsurf/types.js +1 -0
  85. package/dist/utils/config.d.ts +1 -1
  86. package/dist/utils/config.js +1 -1
  87. package/dist/utils/opencode.d.ts +3 -1
  88. package/dist/utils/opencode.js +3 -3
  89. package/dist/utils/platform.d.ts +1 -0
  90. package/dist/utils/platform.js +3 -0
  91. package/package.json +3 -5
  92. package/src/commands/configure.ts +107 -12
  93. package/src/commands/daemon.ts +112 -13
  94. package/src/commands/doctor.ts +1 -1
  95. package/src/commands/init.ts +72 -5
  96. package/src/commands/login.ts +98 -0
  97. package/src/commands/logout.ts +15 -0
  98. package/src/commands/start.ts +4 -4
  99. package/src/core/config.ts +45 -0
  100. package/src/core/daemon-session.ts +199 -0
  101. package/src/core/daemon.ts +206 -0
  102. package/src/core/formatter.ts +56 -0
  103. package/src/core/index.ts +9 -0
  104. package/src/core/parser.ts +47 -0
  105. package/src/core/registry.ts +62 -0
  106. package/src/core/server.ts +211 -0
  107. package/src/core/session.ts +54 -0
  108. package/src/core/types.ts +100 -0
  109. package/src/index.ts +22 -4
  110. package/src/oauth/device-flow.ts +111 -0
  111. package/src/oauth/flow.ts +94 -0
  112. package/src/oauth/index.ts +6 -0
  113. package/src/oauth/lifecycle.ts +77 -0
  114. package/src/oauth/providers.ts +21 -0
  115. package/src/oauth/storage-file.ts +77 -0
  116. package/src/oauth/storage.ts +6 -0
  117. package/src/oauth/types.ts +50 -0
  118. package/src/plugins/copilot/auth.ts +39 -0
  119. package/src/plugins/copilot/index.ts +5 -0
  120. package/src/plugins/copilot/plugin.ts +31 -0
  121. package/src/plugins/copilot/session.ts +130 -0
  122. package/src/plugins/copilot/tools.ts +21 -0
  123. package/src/plugins/copilot/types.ts +43 -0
  124. package/src/plugins/cursor/index.ts +2 -0
  125. package/src/plugins/cursor/plugin.ts +37 -0
  126. package/src/plugins/cursor/session.ts +78 -0
  127. package/src/plugins/cursor/tools.ts +25 -0
  128. package/src/plugins/windsurf/auth.ts +23 -0
  129. package/src/plugins/windsurf/daemon.ts +24 -0
  130. package/src/plugins/windsurf/index.ts +5 -0
  131. package/src/plugins/windsurf/models.ts +44 -0
  132. package/src/plugins/windsurf/plugin.ts +34 -0
  133. package/src/plugins/windsurf/session.ts +8 -0
  134. package/src/plugins/windsurf/tools.ts +13 -0
  135. package/src/plugins/windsurf/types.ts +24 -0
  136. package/src/utils/config.ts +1 -1
  137. package/src/utils/opencode.ts +4 -3
  138. package/src/utils/platform.ts +3 -0
  139. package/test/configure.test.ts +19 -4
  140. package/test/daemon.test.ts +224 -0
@@ -1,4 +1,4 @@
1
1
 
2
- > @ai-ide-bridge/cli@1.0.5 build /home/runner/work/llm-bridge/llm-bridge/cli
2
+ > @ai-ide-bridge/cli@1.1.1 build /home/runner/work/llm-bridge/llm-bridge/cli
3
3
  > tsc
4
4
 
@@ -1,5 +1,14 @@
1
1
  import { findOpencodeConfig, injectProvider } from '../utils/opencode.js';
2
2
  import { readConfig } from '../utils/config.js';
3
+ import { createInterface } from 'node:readline';
4
+ import { CursorBridgePlugin } from '../plugins/cursor/index.js';
5
+ import { CopilotBridgePlugin } from '../plugins/copilot/index.js';
6
+ import { WindsurfBridgePlugin } from '../plugins/windsurf/index.js';
7
+ const PROVIDER_PLUGINS = {
8
+ cursor: CursorBridgePlugin,
9
+ copilot: CopilotBridgePlugin,
10
+ windsurf: WindsurfBridgePlugin,
11
+ };
3
12
  export async function configureOpencodeCommand() {
4
13
  const configPath = findOpencodeConfig();
5
14
  if (!configPath) {
@@ -7,15 +16,74 @@ export async function configureOpencodeCommand() {
7
16
  process.exit(1);
8
17
  }
9
18
  const bridgeConfig = readConfig();
19
+ const configuredPlugins = Object.keys(bridgeConfig.plugins || {});
20
+ if (configuredPlugins.length === 0) {
21
+ console.error('No plugins configured. Run `llm-bridge init` first.');
22
+ process.exit(1);
23
+ }
24
+ const allSelectedModels = {};
25
+ let firstSelectedModelId = null;
26
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
27
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
28
+ for (const pluginName of configuredPlugins) {
29
+ const PluginClass = PROVIDER_PLUGINS[pluginName];
30
+ if (!PluginClass) {
31
+ console.warn(`Skipping unknown provider: ${pluginName}`);
32
+ continue;
33
+ }
34
+ const plugin = new PluginClass();
35
+ const pluginConfig = bridgeConfig.plugins[pluginName];
36
+ let models;
37
+ try {
38
+ models = await plugin.listModels(pluginConfig);
39
+ }
40
+ catch (e) {
41
+ console.warn(`Failed to list models for ${pluginName}: ${e instanceof Error ? e.message : String(e)}`);
42
+ continue;
43
+ }
44
+ if (models.length === 0) {
45
+ console.warn(`No models available for ${pluginName}.`);
46
+ continue;
47
+ }
48
+ console.log(`\nModels available for ${pluginName}:`);
49
+ models.forEach((m, i) => {
50
+ console.log(` ${i + 1}) ${pluginName}/${m.id}`);
51
+ });
52
+ const input = await ask(`Select models (comma-separated numbers, 'all', or 'skip'): `);
53
+ let selectedIndices = [];
54
+ if (input.toLowerCase() === 'all') {
55
+ selectedIndices = models.map((_, i) => i);
56
+ }
57
+ else if (input.toLowerCase() === 'skip') {
58
+ continue;
59
+ }
60
+ else {
61
+ selectedIndices = input
62
+ .split(',')
63
+ .map((s) => parseInt(s.trim(), 10) - 1)
64
+ .filter((i) => i >= 0 && i < models.length);
65
+ }
66
+ if (selectedIndices.length === 0) {
67
+ console.log(`No models selected for ${pluginName}. Skipping.`);
68
+ continue;
69
+ }
70
+ for (const idx of selectedIndices) {
71
+ const model = models[idx];
72
+ const qualifiedId = `${pluginName}/${model.id}`;
73
+ allSelectedModels[qualifiedId] = { name: qualifiedId };
74
+ if (!firstSelectedModelId) {
75
+ firstSelectedModelId = qualifiedId;
76
+ }
77
+ }
78
+ }
79
+ rl.close();
80
+ if (Object.keys(allSelectedModels).length === 0) {
81
+ console.error('No models selected. Run `llm-bridge configure` again to try again.');
82
+ process.exit(1);
83
+ }
10
84
  const providerId = 'llm-bridge';
11
- const plugin = bridgeConfig.defaultPlugin ?? 'cursor';
12
- const modelId = plugin === 'copilot'
13
- ? 'copilot/gpt-4o-copilot'
14
- : plugin === 'windsurf'
15
- ? 'windsurf/claude-4.5-sonnet'
16
- : 'cursor/composer-2';
17
- injectProvider(configPath, providerId, modelId, bridgeConfig.port);
18
- console.log(`Injected provider into ${configPath}`);
19
- console.log(`Provider: ${providerId}, Model: ${modelId}, Port: ${bridgeConfig.port}`);
20
- console.log(`Plugin: ${plugin}`);
85
+ injectProvider(configPath, providerId, allSelectedModels, bridgeConfig.port, firstSelectedModelId);
86
+ const modelCount = Object.keys(allSelectedModels).length;
87
+ console.log(`\nInjected provider into ${configPath}`);
88
+ console.log(`Provider: ${providerId}, Models: ${modelCount}, Default: ${firstSelectedModelId}`);
21
89
  }
@@ -3,3 +3,4 @@ export declare function uninstallDaemonCommand(): Promise<void>;
3
3
  export declare function daemonStatusCommand(): Promise<void>;
4
4
  export declare function daemonDownloadCommand(): Promise<void>;
5
5
  export declare function daemonLocateCommand(): Promise<void>;
6
+ export declare function daemonReloadCommand(): Promise<void>;
@@ -2,17 +2,25 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import { execSync } from 'node:child_process';
5
- import { fileURLToPath } from 'node:url';
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
5
+ import { createWindsurfDaemon } from '../plugins/windsurf/daemon.js';
6
+ import { getPlatform } from '../utils/platform.js';
8
7
  const LABEL = 'com.llm-bridge.daemon';
9
8
  export async function installDaemonCommand() {
10
- if (process.platform !== 'darwin') {
11
- console.error('Daemon installation is only supported on macOS.');
9
+ const platform = getPlatform();
10
+ if (platform === 'darwin') {
11
+ await installMacOSDaemon();
12
+ }
13
+ else if (platform === 'linux') {
14
+ await installLinuxDaemon();
15
+ }
16
+ else {
17
+ console.error('Daemon installation is only supported on macOS and Linux.');
12
18
  process.exit(1);
13
19
  }
20
+ }
21
+ async function installMacOSDaemon() {
14
22
  const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
15
- const wrapperPath = path.join(__dirname, '..', '..', 'scripts', 'llm-bridge-daemon.sh');
23
+ const wrapperPath = path.join(path.dirname(process.execPath), '..', 'scripts', 'llm-bridge-daemon.sh');
16
24
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
17
25
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18
26
  <plist version="1.0">
@@ -35,8 +43,14 @@ export async function installDaemonCommand() {
35
43
  <string>${os.homedir()}/Library/Logs/llm-bridge.err.log</string>
36
44
  </dict>
37
45
  </plist>`;
38
- fs.mkdirSync(path.dirname(plistPath), { recursive: true });
39
- fs.writeFileSync(plistPath, plist);
46
+ try {
47
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
48
+ fs.writeFileSync(plistPath, plist);
49
+ }
50
+ catch (e) {
51
+ console.error('Failed to write plist file:', e);
52
+ process.exit(1);
53
+ }
40
54
  try {
41
55
  execSync(`launchctl bootstrap "gui/$(id -u)" "${plistPath}"`, { stdio: 'inherit' });
42
56
  console.log(`Installed LaunchAgent: ${plistPath}`);
@@ -47,11 +61,56 @@ export async function installDaemonCommand() {
47
61
  process.exit(1);
48
62
  }
49
63
  }
64
+ async function installLinuxDaemon() {
65
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
66
+ const servicePath = path.join(serviceDir, 'llm-bridge.service');
67
+ const binaryPath = process.execPath;
68
+ const unit = `[Unit]
69
+ Description=llm-bridge daemon
70
+ After=network.target
71
+
72
+ [Service]
73
+ Type=simple
74
+ ExecStart=${binaryPath} start
75
+ Restart=on-failure
76
+ RestartSec=5
77
+
78
+ [Install]
79
+ WantedBy=default.target
80
+ `;
81
+ try {
82
+ fs.mkdirSync(serviceDir, { recursive: true });
83
+ fs.writeFileSync(servicePath, unit);
84
+ }
85
+ catch (e) {
86
+ console.error('Failed to write service file:', e);
87
+ process.exit(1);
88
+ }
89
+ try {
90
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
91
+ execSync('systemctl --user enable --now llm-bridge', { stdio: 'inherit' });
92
+ console.log(`Installed systemd user service: ${servicePath}`);
93
+ console.log('Logs: journalctl --user -u llm-bridge -f');
94
+ }
95
+ catch (e) {
96
+ console.error('Failed to enable systemd service:', e);
97
+ process.exit(1);
98
+ }
99
+ }
50
100
  export async function uninstallDaemonCommand() {
51
- if (process.platform !== 'darwin') {
52
- console.error('Daemon uninstallation is only supported on macOS.');
101
+ const platform = getPlatform();
102
+ if (platform === 'darwin') {
103
+ await uninstallMacOSDaemon();
104
+ }
105
+ else if (platform === 'linux') {
106
+ await uninstallLinuxDaemon();
107
+ }
108
+ else {
109
+ console.error('Daemon uninstallation is only supported on macOS and Linux.');
53
110
  process.exit(1);
54
111
  }
112
+ }
113
+ async function uninstallMacOSDaemon() {
55
114
  const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
56
115
  try {
57
116
  execSync(`launchctl bootout "gui/$(id -u)" "${plistPath}" 2>/dev/null || true`, {
@@ -69,8 +128,31 @@ export async function uninstallDaemonCommand() {
69
128
  console.log('No LaunchAgent found.');
70
129
  }
71
130
  }
131
+ async function uninstallLinuxDaemon() {
132
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'llm-bridge.service');
133
+ try {
134
+ execSync('systemctl --user disable --now llm-bridge 2>/dev/null || true', {
135
+ stdio: 'inherit',
136
+ });
137
+ }
138
+ catch {
139
+ // Ignore errors during disable
140
+ }
141
+ if (fs.existsSync(servicePath)) {
142
+ fs.unlinkSync(servicePath);
143
+ console.log(`Removed systemd service: ${servicePath}`);
144
+ }
145
+ else {
146
+ console.log('No systemd service found.');
147
+ }
148
+ try {
149
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
150
+ }
151
+ catch {
152
+ // Ignore errors during reload
153
+ }
154
+ }
72
155
  export async function daemonStatusCommand() {
73
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
74
156
  const daemon = createWindsurfDaemon();
75
157
  const path = await daemon.locate();
76
158
  if (path) {
@@ -84,7 +166,6 @@ export async function daemonStatusCommand() {
84
166
  }
85
167
  }
86
168
  export async function daemonDownloadCommand() {
87
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
88
169
  const daemon = createWindsurfDaemon();
89
170
  console.log('Downloading Windsurf language server...');
90
171
  try {
@@ -97,7 +178,6 @@ export async function daemonDownloadCommand() {
97
178
  }
98
179
  }
99
180
  export async function daemonLocateCommand() {
100
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
101
181
  const daemon = createWindsurfDaemon();
102
182
  const path = await daemon.locate();
103
183
  if (path) {
@@ -108,3 +188,17 @@ export async function daemonLocateCommand() {
108
188
  process.exit(1);
109
189
  }
110
190
  }
191
+ export async function daemonReloadCommand() {
192
+ if (getPlatform() !== 'linux') {
193
+ console.error('Daemon reload is only supported on Linux.');
194
+ process.exit(1);
195
+ }
196
+ try {
197
+ execSync('systemctl --user reload-or-restart llm-bridge', { stdio: 'inherit' });
198
+ console.log('Daemon reloaded.');
199
+ }
200
+ catch (e) {
201
+ console.error('Failed to reload daemon:', e);
202
+ process.exit(1);
203
+ }
204
+ }
@@ -1,7 +1,7 @@
1
1
  import http from 'node:http';
2
2
  import { readConfig } from '../utils/config.js';
3
3
  import fs from 'node:fs';
4
- import { configPath } from '@ai-ide-bridge/core';
4
+ import { configPath } from '../core/index.js';
5
5
  export async function doctorCommand() {
6
6
  const config = readConfig();
7
7
  console.log('llm-bridge diagnostics\n');
@@ -1,8 +1,10 @@
1
1
  import { setPluginConfig, writeConfig, readConfig } from '../utils/config.js';
2
- import { CursorBridgePlugin } from '@ai-ide-bridge/cursor';
3
- import { CopilotBridgePlugin } from '@ai-ide-bridge/copilot';
4
- import { WindsurfBridgePlugin } from '@ai-ide-bridge/windsurf';
2
+ import { CursorBridgePlugin } from '../plugins/cursor/index.js';
3
+ import { CopilotBridgePlugin } from '../plugins/copilot/index.js';
4
+ import { WindsurfBridgePlugin } from '../plugins/windsurf/index.js';
5
5
  import { createInterface } from 'node:readline';
6
+ import { stdin as processStdin, stdout as processStdout } from 'node:process';
7
+ import { loginCommand } from './login.js';
6
8
  const PROVIDERS = ['cursor', 'copilot', 'windsurf'];
7
9
  async function getProviderPlugin(provider) {
8
10
  switch (provider) {
@@ -19,7 +21,7 @@ function getCredentialPrompt(provider) {
19
21
  case 'cursor':
20
22
  return 'Enter your CURSOR_API_KEY: ';
21
23
  case 'copilot':
22
- return 'Enter your GITHUB_TOKEN: ';
24
+ return 'Enter your GITHUB_TOKEN (or leave empty for OAuth): ';
23
25
  case 'windsurf':
24
26
  return 'Enter your WINDSURF_TOKEN: ';
25
27
  }
@@ -34,6 +36,52 @@ function getEnvVar(provider) {
34
36
  return 'WINDSURF_TOKEN';
35
37
  }
36
38
  }
39
+ async function authenticateWithOAuth(provider) {
40
+ if (provider === 'copilot') {
41
+ await loginCommand('copilot');
42
+ return true;
43
+ }
44
+ console.log(`OAuth not available for ${provider}. Use token authentication instead.`);
45
+ return false;
46
+ }
47
+ async function askHidden(prompt) {
48
+ return new Promise((resolve, reject) => {
49
+ const stdin = processStdin;
50
+ const stdout = processStdout;
51
+ stdout.write(prompt);
52
+ const wasRaw = stdin.isRaw;
53
+ stdin.setRawMode(true);
54
+ stdin.resume();
55
+ let input = '';
56
+ const onData = (data) => {
57
+ const char = data.toString();
58
+ if (char === '\r' || char === '\n') {
59
+ stdin.setRawMode(wasRaw);
60
+ stdin.pause();
61
+ stdin.removeListener('data', onData);
62
+ stdout.write('\n');
63
+ resolve(input);
64
+ }
65
+ else if (char === '\x7f' || char === '\b') {
66
+ if (input.length > 0) {
67
+ input = input.slice(0, -1);
68
+ stdout.write('\b \b');
69
+ }
70
+ }
71
+ else if (char === '\x03') {
72
+ stdin.setRawMode(wasRaw);
73
+ stdin.pause();
74
+ stdin.removeListener('data', onData);
75
+ process.exit(1);
76
+ }
77
+ else {
78
+ input += char;
79
+ stdout.write('*');
80
+ }
81
+ };
82
+ stdin.on('data', onData);
83
+ });
84
+ }
37
85
  export async function initCommand() {
38
86
  const rl = createInterface({ input: process.stdin, output: process.stdout });
39
87
  const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
@@ -55,8 +103,25 @@ export async function initCommand() {
55
103
  break;
56
104
  }
57
105
  const provider = input;
106
+ if (provider === 'copilot') {
107
+ const method = await ask('Auth method (token/oauth): ');
108
+ if (method.toLowerCase() === 'oauth') {
109
+ const ok = await authenticateWithOAuth(provider);
110
+ if (!ok)
111
+ continue;
112
+ console.log(`Authentication successful for ${provider}.`);
113
+ setPluginConfig(provider, { COPILOT_OAUTH: 'true' });
114
+ configuredProviders.push(provider);
115
+ if (!firstProvider)
116
+ firstProvider = provider;
117
+ const more = await ask('Configure another provider? (y/n): ');
118
+ if (more.toLowerCase() !== 'y')
119
+ break;
120
+ continue;
121
+ }
122
+ }
58
123
  const envVar = getEnvVar(provider);
59
- const credential = await ask(getCredentialPrompt(provider));
124
+ const credential = await askHidden(getCredentialPrompt(provider));
60
125
  if (!credential) {
61
126
  console.error('Credential is required.');
62
127
  continue;
@@ -0,0 +1 @@
1
+ export declare function loginCommand(provider?: string): Promise<void>;
@@ -0,0 +1,62 @@
1
+ import { createInterface } from 'node:readline';
2
+ import { createTokenStore, DeviceFlow, providers } from '../oauth/index.js';
3
+ const SUPPORTED_PROVIDERS = ['copilot', 'cursor'];
4
+ export async function loginCommand(provider) {
5
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
6
+ const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
7
+ let providerId;
8
+ if (provider && SUPPORTED_PROVIDERS.includes(provider)) {
9
+ providerId = provider;
10
+ }
11
+ else {
12
+ const input = await ask(`Provider to login (${SUPPORTED_PROVIDERS.join(', ')}): `);
13
+ if (!SUPPORTED_PROVIDERS.includes(input)) {
14
+ console.error(`Unknown provider: ${input}`);
15
+ rl.close();
16
+ process.exit(1);
17
+ }
18
+ providerId = input;
19
+ }
20
+ const oauthProvider = providers[providerId];
21
+ if (!oauthProvider) {
22
+ console.error(`OAuth not configured for provider: ${providerId}`);
23
+ rl.close();
24
+ process.exit(1);
25
+ }
26
+ const store = createTokenStore();
27
+ if (oauthProvider.deviceFlow) {
28
+ await loginWithDeviceFlow(rl, oauthProvider, store);
29
+ }
30
+ else {
31
+ await loginWithPKCE(rl, oauthProvider, store);
32
+ }
33
+ rl.close();
34
+ }
35
+ async function loginWithDeviceFlow(rl, provider, store) {
36
+ const config = {
37
+ provider,
38
+ store,
39
+ };
40
+ const deviceFlow = new DeviceFlow(config);
41
+ console.log(`\nAuthenticating with ${provider.name}...`);
42
+ console.log('Requesting device code...');
43
+ try {
44
+ const deviceCode = await deviceFlow.start();
45
+ console.log(`\nOpen this URL in your browser: ${deviceCode.verificationUri}`);
46
+ console.log(`Enter this code: ${deviceCode.userCode}\n`);
47
+ console.log('Waiting for authorization...');
48
+ const token = await deviceFlow.poll();
49
+ const expiresIn = Math.round((token.expiresAt - Date.now()) / 1000);
50
+ console.log(`\nAuthentication successful!`);
51
+ console.log(`Token stored securely. Expires in ${expiresIn}s.`);
52
+ }
53
+ catch (err) {
54
+ console.error(`\nAuthentication failed: ${err.message}`);
55
+ process.exit(1);
56
+ }
57
+ }
58
+ async function loginWithPKCE(rl, provider, store) {
59
+ console.log(`\nOAuth with PKCE is not yet supported for ${provider.name}.`);
60
+ console.log('Use a personal access token instead: llm-bridge init');
61
+ process.exit(1);
62
+ }
@@ -0,0 +1 @@
1
+ export declare function logoutCommand(provider?: string): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import { createTokenStore } from '../oauth/index.js';
2
+ export async function logoutCommand(provider) {
3
+ const store = createTokenStore();
4
+ if (provider) {
5
+ await store.delete(provider);
6
+ console.log(`Logged out from ${provider}.`);
7
+ return;
8
+ }
9
+ await store.delete('copilot');
10
+ await store.delete('cursor');
11
+ console.log('Logged out from all providers.');
12
+ }
@@ -1,7 +1,7 @@
1
- import { BridgeServer, loadConfig } from '@ai-ide-bridge/core';
2
- import { CursorBridgePlugin } from '@ai-ide-bridge/cursor';
3
- import { CopilotBridgePlugin } from '@ai-ide-bridge/copilot';
4
- import { WindsurfBridgePlugin } from '@ai-ide-bridge/windsurf';
1
+ import { BridgeServer, loadConfig } from '../core/index.js';
2
+ import { CursorBridgePlugin } from '../plugins/cursor/index.js';
3
+ import { CopilotBridgePlugin } from '../plugins/copilot/index.js';
4
+ import { WindsurfBridgePlugin } from '../plugins/windsurf/index.js';
5
5
  async function getPlugin(name) {
6
6
  switch (name) {
7
7
  case 'cursor':
@@ -0,0 +1,4 @@
1
+ import { BridgeConfig } from './types.js';
2
+ export declare function configPath(): string;
3
+ export declare function loadConfig(): BridgeConfig;
4
+ export declare function saveConfig(config: BridgeConfig): void;
@@ -0,0 +1,43 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { DefaultConfig } from './types.js';
5
+ export function configPath() {
6
+ const home = os.homedir();
7
+ return path.join(home, '.config', 'llm-bridge', 'config.json');
8
+ }
9
+ export function loadConfig() {
10
+ const envPort = process.env.LLM_BRIDGE_PORT;
11
+ const envHost = process.env.LLM_BRIDGE_HOST;
12
+ try {
13
+ const filePath = process.env.LLM_BRIDGE_CONFIG ?? configPath();
14
+ if (fs.existsSync(filePath)) {
15
+ const raw = fs.readFileSync(filePath, 'utf8');
16
+ const fileConfig = JSON.parse(raw);
17
+ const config = { ...DefaultConfig, ...fileConfig };
18
+ if (fileConfig.activePlugin && !fileConfig.defaultPlugin) {
19
+ config.defaultPlugin = fileConfig.activePlugin;
20
+ }
21
+ if (envPort)
22
+ config.port = parseInt(envPort, 10);
23
+ if (envHost)
24
+ config.host = envHost;
25
+ return config;
26
+ }
27
+ }
28
+ catch (err) {
29
+ console.warn('[llm-bridge] failed to load config file, using defaults:', err);
30
+ }
31
+ const config = { ...DefaultConfig };
32
+ if (envPort)
33
+ config.port = parseInt(envPort, 10);
34
+ if (envHost)
35
+ config.host = envHost;
36
+ return config;
37
+ }
38
+ export function saveConfig(config) {
39
+ const filePath = configPath();
40
+ const dir = path.dirname(filePath);
41
+ fs.mkdirSync(dir, { recursive: true });
42
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
43
+ }
@@ -0,0 +1,14 @@
1
+ import type { BridgeSession, Message, ToolDefinition, StreamChunk } from './types.js';
2
+ import type { DaemonManager } from './daemon.js';
3
+ export declare class DaemonBridgeSession implements BridgeSession {
4
+ private daemon;
5
+ private token;
6
+ private model;
7
+ private cwd;
8
+ private proc;
9
+ private requestId;
10
+ private busy;
11
+ constructor(daemon: DaemonManager, token: string, model: string, cwd: string);
12
+ send(messages: Message[], tools?: ToolDefinition[]): AsyncIterable<StreamChunk>;
13
+ dispose(): Promise<void>;
14
+ }