@ai-ide-bridge/cli 1.0.4 → 1.1.0

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 (134) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/commands/daemon.d.ts +1 -0
  3. package/dist/commands/daemon.js +107 -13
  4. package/dist/commands/doctor.js +1 -1
  5. package/dist/commands/init.js +30 -4
  6. package/dist/commands/login.d.ts +1 -0
  7. package/dist/commands/login.js +62 -0
  8. package/dist/commands/logout.d.ts +1 -0
  9. package/dist/commands/logout.js +12 -0
  10. package/dist/commands/start.js +4 -4
  11. package/dist/core/config.d.ts +4 -0
  12. package/dist/core/config.js +43 -0
  13. package/dist/core/daemon-session.d.ts +14 -0
  14. package/dist/core/daemon-session.js +179 -0
  15. package/dist/core/daemon.d.ts +16 -0
  16. package/dist/core/daemon.js +168 -0
  17. package/dist/core/formatter.d.ts +3 -0
  18. package/dist/core/formatter.js +44 -0
  19. package/dist/core/index.d.ts +9 -0
  20. package/dist/core/index.js +9 -0
  21. package/dist/core/parser.d.ts +164 -0
  22. package/dist/core/parser.js +37 -0
  23. package/dist/core/registry.d.ts +16 -0
  24. package/dist/core/registry.js +53 -0
  25. package/dist/core/server.d.ts +19 -0
  26. package/dist/core/server.js +185 -0
  27. package/dist/core/session.d.ts +11 -0
  28. package/dist/core/session.js +39 -0
  29. package/dist/core/types.d.ts +166 -0
  30. package/dist/core/types.js +44 -0
  31. package/dist/index.js +22 -5
  32. package/dist/oauth/device-flow.d.ts +12 -0
  33. package/dist/oauth/device-flow.js +93 -0
  34. package/dist/oauth/flow.d.ts +11 -0
  35. package/dist/oauth/flow.js +75 -0
  36. package/dist/oauth/index.d.ts +6 -0
  37. package/dist/oauth/index.js +5 -0
  38. package/dist/oauth/lifecycle.d.ts +13 -0
  39. package/dist/oauth/lifecycle.js +56 -0
  40. package/dist/oauth/providers.d.ts +2 -0
  41. package/dist/oauth/providers.js +19 -0
  42. package/dist/oauth/storage-file.d.ts +2 -0
  43. package/dist/oauth/storage-file.js +68 -0
  44. package/dist/oauth/storage.d.ts +2 -0
  45. package/dist/oauth/storage.js +4 -0
  46. package/dist/oauth/types.d.ts +44 -0
  47. package/dist/oauth/types.js +1 -0
  48. package/dist/plugins/copilot/auth.d.ts +7 -0
  49. package/dist/plugins/copilot/auth.js +30 -0
  50. package/dist/plugins/copilot/index.d.ts +5 -0
  51. package/dist/plugins/copilot/index.js +4 -0
  52. package/dist/plugins/copilot/plugin.d.ts +8 -0
  53. package/dist/plugins/copilot/plugin.js +29 -0
  54. package/dist/plugins/copilot/session.d.ts +8 -0
  55. package/dist/plugins/copilot/session.js +115 -0
  56. package/dist/plugins/copilot/tools.d.ts +10 -0
  57. package/dist/plugins/copilot/tools.js +10 -0
  58. package/dist/plugins/copilot/types.d.ts +15 -0
  59. package/dist/plugins/copilot/types.js +27 -0
  60. package/dist/plugins/cursor/index.d.ts +2 -0
  61. package/dist/plugins/cursor/index.js +2 -0
  62. package/dist/plugins/cursor/plugin.d.ts +8 -0
  63. package/dist/plugins/cursor/plugin.js +36 -0
  64. package/dist/plugins/cursor/session.d.ts +11 -0
  65. package/dist/plugins/cursor/session.js +69 -0
  66. package/dist/plugins/cursor/tools.d.ts +11 -0
  67. package/dist/plugins/cursor/tools.js +13 -0
  68. package/dist/plugins/windsurf/auth.d.ts +3 -0
  69. package/dist/plugins/windsurf/auth.js +20 -0
  70. package/dist/plugins/windsurf/daemon.d.ts +6 -0
  71. package/dist/plugins/windsurf/daemon.js +16 -0
  72. package/dist/plugins/windsurf/index.d.ts +5 -0
  73. package/dist/plugins/windsurf/index.js +4 -0
  74. package/dist/plugins/windsurf/models.d.ts +2 -0
  75. package/dist/plugins/windsurf/models.js +42 -0
  76. package/dist/plugins/windsurf/plugin.d.ts +8 -0
  77. package/dist/plugins/windsurf/plugin.js +31 -0
  78. package/dist/plugins/windsurf/session.d.ts +5 -0
  79. package/dist/plugins/windsurf/session.js +6 -0
  80. package/dist/plugins/windsurf/tools.d.ts +3 -0
  81. package/dist/plugins/windsurf/tools.js +10 -0
  82. package/dist/plugins/windsurf/types.d.ts +22 -0
  83. package/dist/plugins/windsurf/types.js +1 -0
  84. package/dist/utils/config.d.ts +1 -1
  85. package/dist/utils/config.js +1 -1
  86. package/dist/utils/platform.d.ts +1 -0
  87. package/dist/utils/platform.js +3 -0
  88. package/package.json +3 -5
  89. package/src/commands/daemon.ts +112 -13
  90. package/src/commands/doctor.ts +1 -1
  91. package/src/commands/init.ts +29 -4
  92. package/src/commands/login.ts +98 -0
  93. package/src/commands/logout.ts +15 -0
  94. package/src/commands/start.ts +4 -4
  95. package/src/core/config.ts +45 -0
  96. package/src/core/daemon-session.ts +199 -0
  97. package/src/core/daemon.ts +206 -0
  98. package/src/core/formatter.ts +56 -0
  99. package/src/core/index.ts +9 -0
  100. package/src/core/parser.ts +47 -0
  101. package/src/core/registry.ts +62 -0
  102. package/src/core/server.ts +211 -0
  103. package/src/core/session.ts +54 -0
  104. package/src/core/types.ts +100 -0
  105. package/src/index.ts +22 -4
  106. package/src/oauth/device-flow.ts +111 -0
  107. package/src/oauth/flow.ts +94 -0
  108. package/src/oauth/index.ts +6 -0
  109. package/src/oauth/lifecycle.ts +77 -0
  110. package/src/oauth/providers.ts +21 -0
  111. package/src/oauth/storage-file.ts +77 -0
  112. package/src/oauth/storage.ts +6 -0
  113. package/src/oauth/types.ts +50 -0
  114. package/src/plugins/copilot/auth.ts +39 -0
  115. package/src/plugins/copilot/index.ts +5 -0
  116. package/src/plugins/copilot/plugin.ts +31 -0
  117. package/src/plugins/copilot/session.ts +130 -0
  118. package/src/plugins/copilot/tools.ts +21 -0
  119. package/src/plugins/copilot/types.ts +43 -0
  120. package/src/plugins/cursor/index.ts +2 -0
  121. package/src/plugins/cursor/plugin.ts +37 -0
  122. package/src/plugins/cursor/session.ts +78 -0
  123. package/src/plugins/cursor/tools.ts +25 -0
  124. package/src/plugins/windsurf/auth.ts +23 -0
  125. package/src/plugins/windsurf/daemon.ts +24 -0
  126. package/src/plugins/windsurf/index.ts +5 -0
  127. package/src/plugins/windsurf/models.ts +44 -0
  128. package/src/plugins/windsurf/plugin.ts +34 -0
  129. package/src/plugins/windsurf/session.ts +8 -0
  130. package/src/plugins/windsurf/tools.ts +13 -0
  131. package/src/plugins/windsurf/types.ts +24 -0
  132. package/src/utils/config.ts +1 -1
  133. package/src/utils/platform.ts +3 -0
  134. package/test/daemon.test.ts +224 -0
@@ -0,0 +1,24 @@
1
+ import { createDaemonManager, type DaemonManager } from '../../core/index.js';
2
+ import { homedir, platform, arch } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const DEFAULT_KNOWN_PATHS: string[] = [
6
+ join('/Applications', 'Windsurf.app', 'Contents', 'Resources', 'language_server'),
7
+ join('/usr', 'local', 'bin', 'language_server'),
8
+ ];
9
+
10
+ interface WindsurfDaemonOptions {
11
+ knownPaths?: string[];
12
+ }
13
+
14
+ export function createWindsurfDaemon(options: WindsurfDaemonOptions = {}): DaemonManager {
15
+ const knownPaths = options.knownPaths ?? DEFAULT_KNOWN_PATHS;
16
+
17
+ return createDaemonManager({
18
+ binaryName: 'language_server',
19
+ downloadUrl: `https://server.codeium.com/language_server/latest/{platform}/{arch}`,
20
+ checksum: 'PLACEHOLDER_SHA256',
21
+ knownPaths,
22
+ envVar: 'WINDSURF_LANGUAGE_SERVER_PATH',
23
+ });
24
+ }
@@ -0,0 +1,5 @@
1
+ export { WindsurfBridgePlugin } from './plugin.js';
2
+ export { WINDSURF_MODELS } from './models.js';
3
+ export { createWindsurfDaemon } from './daemon.js';
4
+ export { WindsurfBridgeSession } from './session.js';
5
+ export type { WindsurfModel, WindsurfConfig } from './types.js';
@@ -0,0 +1,44 @@
1
+ import type { WindsurfModel } from './types.js';
2
+
3
+ export const WINDSURF_MODELS: WindsurfModel[] = [
4
+ {
5
+ id: 'claude-4.5-sonnet',
6
+ name: 'Claude 4.5 Sonnet (Windsurf)',
7
+ capabilities: { streaming: true, tools: true },
8
+ },
9
+ {
10
+ id: 'claude-4.5-opus',
11
+ name: 'Claude 4.5 Opus (Windsurf)',
12
+ capabilities: { streaming: true, tools: true },
13
+ },
14
+ {
15
+ id: 'gpt-5.2',
16
+ name: 'GPT-5.2 (Windsurf)',
17
+ capabilities: { streaming: true, tools: true },
18
+ },
19
+ {
20
+ id: 'gpt-5.2-codex',
21
+ name: 'GPT-5.2 Codex (Windsurf)',
22
+ capabilities: { streaming: true, tools: true },
23
+ },
24
+ {
25
+ id: 'gpt-4o',
26
+ name: 'GPT-4o (Windsurf)',
27
+ capabilities: { streaming: true, tools: true },
28
+ },
29
+ {
30
+ id: 'gemini-3.0-pro',
31
+ name: 'Gemini 3.0 Pro (Windsurf)',
32
+ capabilities: { streaming: true, tools: true },
33
+ },
34
+ {
35
+ id: 'gemini-3.0-flash',
36
+ name: 'Gemini 3.0 Flash (Windsurf)',
37
+ capabilities: { streaming: true, tools: true },
38
+ },
39
+ {
40
+ id: 'swe-1.5',
41
+ name: 'SWE-1.5 (Windsurf)',
42
+ capabilities: { streaming: true, tools: true },
43
+ },
44
+ ];
@@ -0,0 +1,34 @@
1
+ import type { BridgePlugin, BridgeSession, ModelInfo } from '../../core/index.js';
2
+ import { WindsurfBridgeSession } from './session.js';
3
+ import { WINDSURF_MODELS } from './models.js';
4
+ import type { WindsurfConfig } from './types.js';
5
+ import { getToken, validateToken } from './auth.js';
6
+ import { createWindsurfDaemon } from './daemon.js';
7
+
8
+ export class WindsurfBridgePlugin implements BridgePlugin {
9
+ name = 'windsurf';
10
+ version = '2.0.0';
11
+
12
+ async authenticate(config: Record<string, string>): Promise<boolean> {
13
+ const token = getToken(config as WindsurfConfig);
14
+ if (!token) return false;
15
+ return validateToken(token);
16
+ }
17
+
18
+ async listModels(config: Record<string, string>): Promise<ModelInfo[]> {
19
+ const token = getToken(config as WindsurfConfig);
20
+ if (!token) throw new Error('Missing WINDSURF_TOKEN');
21
+ return WINDSURF_MODELS.map((m) => ({
22
+ id: m.id,
23
+ name: m.name,
24
+ capabilities: m.capabilities,
25
+ }));
26
+ }
27
+
28
+ async createSession(config: Record<string, string>, model: string): Promise<BridgeSession> {
29
+ const token = getToken(config as WindsurfConfig);
30
+ if (!token) throw new Error('Missing WINDSURF_TOKEN');
31
+ const daemon = createWindsurfDaemon();
32
+ return new WindsurfBridgeSession(daemon, token, model);
33
+ }
34
+ }
@@ -0,0 +1,8 @@
1
+ import { DaemonBridgeSession } from '../../core/index.js';
2
+ import type { DaemonManager } from '../../core/index.js';
3
+
4
+ export class WindsurfBridgeSession extends DaemonBridgeSession {
5
+ constructor(daemon: DaemonManager, token: string, model: string, cwd: string = process.cwd()) {
6
+ super(daemon, token, model, cwd);
7
+ }
8
+ }
@@ -0,0 +1,13 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+ import type { WindsurfTool } from './types.js';
3
+
4
+ export function translateTools(tools: ToolDefinition[]): WindsurfTool[] {
5
+ return tools.map((tool) => ({
6
+ type: 'function' as const,
7
+ function: {
8
+ name: tool.function.name,
9
+ description: tool.function.description,
10
+ parameters: tool.function.parameters as Record<string, unknown>,
11
+ },
12
+ }));
13
+ }
@@ -0,0 +1,24 @@
1
+ export interface WindsurfModel {
2
+ id: string;
3
+ name: string;
4
+ capabilities: {
5
+ streaming: boolean;
6
+ tools: boolean;
7
+ vision?: boolean;
8
+ };
9
+ }
10
+
11
+ export interface WindsurfConfig {
12
+ WINDSURF_TOKEN?: string;
13
+ WINDSURF_OAUTH_TOKEN?: string;
14
+ WINDSURF_LANGUAGE_SERVER_PATH?: string;
15
+ }
16
+
17
+ export interface WindsurfTool {
18
+ type: 'function';
19
+ function: {
20
+ name: string;
21
+ description?: string;
22
+ parameters: Record<string, unknown>;
23
+ };
24
+ }
@@ -1,4 +1,4 @@
1
- import { loadConfig, saveConfig, BridgeConfig } from '@ai-ide-bridge/core';
1
+ import { loadConfig, saveConfig, BridgeConfig } from '../core/index.js';
2
2
 
3
3
  export function readConfig(): BridgeConfig {
4
4
  return loadConfig();
@@ -0,0 +1,3 @@
1
+ export function getPlatform(): NodeJS.Platform {
2
+ return process.platform;
3
+ }
@@ -0,0 +1,224 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ vi.mock('node:child_process', () => ({
4
+ execSync: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('node:fs', () => ({
8
+ default: {
9
+ mkdirSync: vi.fn(),
10
+ writeFileSync: vi.fn(),
11
+ existsSync: vi.fn(),
12
+ unlinkSync: vi.fn(),
13
+ },
14
+ }));
15
+
16
+ vi.mock('../src/utils/platform.js', () => ({
17
+ getPlatform: vi.fn(() => process.platform),
18
+ }));
19
+
20
+ const { execSync } = await import('node:child_process');
21
+ const mockedExecSync = vi.mocked(execSync);
22
+ const { getPlatform } = await import('../src/utils/platform.js');
23
+ const mockedGetPlatform = vi.mocked(getPlatform);
24
+
25
+ describe('installDaemonCommand', () => {
26
+ beforeEach(() => {
27
+ vi.clearAllMocks();
28
+ mockedGetPlatform.mockReturnValue(process.platform);
29
+ });
30
+
31
+ it('generates systemd unit file on Linux', async () => {
32
+ mockedGetPlatform.mockReturnValue('linux');
33
+
34
+ const { installDaemonCommand } = await import('../src/commands/daemon.js');
35
+
36
+ await installDaemonCommand();
37
+
38
+ const fs = await import('node:fs');
39
+ const mockedFs = vi.mocked(fs.default);
40
+
41
+ expect(mockedFs.mkdirSync).toHaveBeenCalled();
42
+ const writeCall = mockedFs.writeFileSync.mock.calls[0];
43
+ const unitPath = writeCall[0] as string;
44
+ expect(unitPath).toContain('.config/systemd/user/llm-bridge.service');
45
+
46
+ const unitContent = writeCall[1] as string;
47
+ expect(unitContent).toContain('[Unit]');
48
+ expect(unitContent).toContain('Description=llm-bridge daemon');
49
+ expect(unitContent).toContain('After=network.target');
50
+ expect(unitContent).toContain('[Service]');
51
+ expect(unitContent).toContain('Type=simple');
52
+ expect(unitContent).toContain('Restart=on-failure');
53
+ expect(unitContent).toContain('RestartSec=5');
54
+ expect(unitContent).toContain('[Install]');
55
+ expect(unitContent).toContain('WantedBy=default.target');
56
+
57
+ expect(mockedExecSync).toHaveBeenCalledWith('systemctl --user daemon-reload', {
58
+ stdio: 'inherit',
59
+ });
60
+ expect(mockedExecSync).toHaveBeenCalledWith('systemctl --user enable --now llm-bridge', {
61
+ stdio: 'inherit',
62
+ });
63
+ });
64
+
65
+ it('routes to installMacOSDaemon on darwin', async () => {
66
+ mockedGetPlatform.mockReturnValue('darwin');
67
+
68
+ const { installDaemonCommand } = await import('../src/commands/daemon.js');
69
+ const fs = await import('node:fs');
70
+ const mockedFs = vi.mocked(fs.default);
71
+
72
+ await installDaemonCommand();
73
+
74
+ expect(mockedFs.writeFileSync).toHaveBeenCalled();
75
+ const writeCall = mockedFs.writeFileSync.mock.calls[0];
76
+ const plistPath = writeCall[0] as string;
77
+ expect(plistPath).toContain('Library/LaunchAgents/com.llm-bridge.daemon.plist');
78
+
79
+ expect(mockedExecSync).toHaveBeenCalledWith(expect.stringContaining('launchctl bootstrap'), {
80
+ stdio: 'inherit',
81
+ });
82
+ });
83
+
84
+ it('exits with error on unsupported platforms', async () => {
85
+ mockedGetPlatform.mockReturnValue('win32');
86
+
87
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
88
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
89
+
90
+ const { installDaemonCommand } = await import('../src/commands/daemon.js');
91
+
92
+ await installDaemonCommand();
93
+
94
+ expect(errorSpy).toHaveBeenCalledWith(
95
+ 'Daemon installation is only supported on macOS and Linux.',
96
+ );
97
+ expect(exitSpy).toHaveBeenCalledWith(1);
98
+
99
+ exitSpy.mockRestore();
100
+ errorSpy.mockRestore();
101
+ });
102
+ });
103
+
104
+ describe('uninstallDaemonCommand', () => {
105
+ beforeEach(() => {
106
+ vi.clearAllMocks();
107
+ mockedGetPlatform.mockReturnValue(process.platform);
108
+ });
109
+
110
+ it('removes systemd unit file on Linux', async () => {
111
+ mockedGetPlatform.mockReturnValue('linux');
112
+
113
+ const { uninstallDaemonCommand } = await import('../src/commands/daemon.js');
114
+ const fs = await import('node:fs');
115
+ const mockedFs = vi.mocked(fs.default);
116
+
117
+ mockedFs.existsSync.mockReturnValue(true);
118
+
119
+ await uninstallDaemonCommand();
120
+
121
+ expect(mockedExecSync).toHaveBeenCalledWith(
122
+ 'systemctl --user disable --now llm-bridge 2>/dev/null || true',
123
+ { stdio: 'inherit' },
124
+ );
125
+
126
+ const unlinkCall = mockedFs.unlinkSync.mock.calls[0];
127
+ expect(unlinkCall[0]).toContain('.config/systemd/user/llm-bridge.service');
128
+
129
+ expect(mockedExecSync).toHaveBeenCalledWith('systemctl --user daemon-reload', {
130
+ stdio: 'inherit',
131
+ });
132
+ });
133
+
134
+ it('routes to uninstallMacOSDaemon on darwin', async () => {
135
+ mockedGetPlatform.mockReturnValue('darwin');
136
+
137
+ const { uninstallDaemonCommand } = await import('../src/commands/daemon.js');
138
+ const fs = await import('node:fs');
139
+ const mockedFs = vi.mocked(fs.default);
140
+
141
+ mockedFs.existsSync.mockReturnValue(true);
142
+
143
+ await uninstallDaemonCommand();
144
+
145
+ const unlinkCall = mockedFs.unlinkSync.mock.calls[0];
146
+ expect(unlinkCall[0]).toContain('Library/LaunchAgents/com.llm-bridge.daemon.plist');
147
+
148
+ expect(mockedExecSync).toHaveBeenCalledWith(expect.stringContaining('launchctl bootout'), {
149
+ stdio: 'inherit',
150
+ });
151
+ });
152
+
153
+ it('errors on unsupported platforms', async () => {
154
+ mockedGetPlatform.mockReturnValue('win32');
155
+
156
+ const { uninstallDaemonCommand } = await import('../src/commands/daemon.js');
157
+
158
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
159
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
160
+
161
+ await uninstallDaemonCommand();
162
+
163
+ expect(exitSpy).toHaveBeenCalledWith(1);
164
+ expect(consoleSpy).toHaveBeenCalledWith(
165
+ 'Daemon uninstallation is only supported on macOS and Linux.',
166
+ );
167
+
168
+ exitSpy.mockRestore();
169
+ consoleSpy.mockRestore();
170
+ });
171
+
172
+ it('handles missing service file gracefully on Linux', async () => {
173
+ mockedGetPlatform.mockReturnValue('linux');
174
+
175
+ const { uninstallDaemonCommand } = await import('../src/commands/daemon.js');
176
+ const fs = await import('node:fs');
177
+ const mockedFs = vi.mocked(fs.default);
178
+
179
+ mockedFs.existsSync.mockReturnValue(false);
180
+
181
+ await uninstallDaemonCommand();
182
+
183
+ expect(mockedFs.unlinkSync).not.toHaveBeenCalled();
184
+ expect(mockedExecSync).toHaveBeenCalledWith('systemctl --user daemon-reload', {
185
+ stdio: 'inherit',
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('daemonReloadCommand', () => {
191
+ beforeEach(() => {
192
+ vi.clearAllMocks();
193
+ mockedGetPlatform.mockReturnValue(process.platform);
194
+ });
195
+
196
+ it('runs systemctl daemon-reload on Linux', async () => {
197
+ mockedGetPlatform.mockReturnValue('linux');
198
+
199
+ const { daemonReloadCommand } = await import('../src/commands/daemon.js');
200
+
201
+ await daemonReloadCommand();
202
+
203
+ expect(mockedExecSync).toHaveBeenCalledWith('systemctl --user reload-or-restart llm-bridge', {
204
+ stdio: 'inherit',
205
+ });
206
+ });
207
+
208
+ it('errors on unsupported platforms', async () => {
209
+ mockedGetPlatform.mockReturnValue('win32');
210
+
211
+ const { daemonReloadCommand } = await import('../src/commands/daemon.js');
212
+
213
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined as never);
214
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
215
+
216
+ await daemonReloadCommand();
217
+
218
+ expect(exitSpy).toHaveBeenCalledWith(1);
219
+ expect(consoleSpy).toHaveBeenCalledWith('Daemon reload is only supported on Linux.');
220
+
221
+ exitSpy.mockRestore();
222
+ consoleSpy.mockRestore();
223
+ });
224
+ });