@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,11 @@
1
+ import type { BridgeSession, Message, ToolDefinition, StreamChunk } from '../../core/index.js';
2
+ export declare class CursorBridgeSession implements BridgeSession {
3
+ private agent;
4
+ private apiKey;
5
+ private modelId;
6
+ private cwd;
7
+ constructor(apiKey: string, modelId: string, cwd?: string);
8
+ send(messages: Message[], tools?: ToolDefinition[]): AsyncIterable<StreamChunk>;
9
+ dispose(): Promise<void>;
10
+ private buildPrompt;
11
+ }
@@ -0,0 +1,69 @@
1
+ import { Agent } from '@cursor/sdk';
2
+ import { translateTools } from './tools.js';
3
+ export class CursorBridgeSession {
4
+ agent = null;
5
+ apiKey;
6
+ modelId;
7
+ cwd;
8
+ constructor(apiKey, modelId, cwd = process.cwd()) {
9
+ this.apiKey = apiKey;
10
+ this.modelId = modelId;
11
+ this.cwd = cwd;
12
+ }
13
+ async *send(messages, tools) {
14
+ const prompt = this.buildPrompt(messages);
15
+ const cursorTools = tools ? translateTools(tools) : undefined;
16
+ try {
17
+ this.agent = await Agent.create({
18
+ apiKey: this.apiKey,
19
+ model: { id: this.modelId },
20
+ local: { cwd: this.cwd, settingSources: [] },
21
+ });
22
+ const sendOptions = {
23
+ model: { id: this.modelId },
24
+ onDelta: ({ update }) => {
25
+ if (update.type === 'text-delta' && 'text' in update && update.text) {
26
+ // onDelta is synchronous callback, we buffer and yield in the loop
27
+ }
28
+ },
29
+ };
30
+ const run = await this.agent.send(prompt, sendOptions);
31
+ const result = await run.wait();
32
+ if (result.status === 'error' || result.status === 'cancelled') {
33
+ yield {
34
+ type: 'error',
35
+ content: `Agent run ${result.status}: ${result.result ?? 'no details'}`,
36
+ finishReason: 'error',
37
+ };
38
+ return;
39
+ }
40
+ yield { type: 'text', content: result.result ?? '', finishReason: 'stop' };
41
+ }
42
+ catch (e) {
43
+ const msg = e instanceof Error ? e.message : String(e);
44
+ yield { type: 'error', content: msg, finishReason: 'error' };
45
+ }
46
+ }
47
+ async dispose() {
48
+ if (this.agent) {
49
+ try {
50
+ await this.agent[Symbol.asyncDispose]();
51
+ }
52
+ catch {
53
+ // Ignore dispose errors
54
+ }
55
+ this.agent = null;
56
+ }
57
+ }
58
+ buildPrompt(messages) {
59
+ const blocks = [];
60
+ for (const m of messages) {
61
+ const text = typeof m.content === 'string' ? m.content : '';
62
+ if (!text)
63
+ continue;
64
+ const label = m.role === 'tool' ? `tool (${m.tool_call_id ?? m.name ?? 'result'})` : m.role;
65
+ blocks.push(`[${label}]\n${text}`);
66
+ }
67
+ return `\nFollow this conversation transcript and reply as the assistant.\n\n${blocks.join('\n\n---\n\n')}\n`;
68
+ }
69
+ }
@@ -0,0 +1,11 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+ export interface CursorTool {
3
+ type: 'function';
4
+ function: {
5
+ name: string;
6
+ description?: string;
7
+ parameters: Record<string, unknown>;
8
+ };
9
+ }
10
+ export declare function translateTools(tools: ToolDefinition[]): CursorTool[];
11
+ export declare function translateToolResult(toolCallId: string, result: string): string;
@@ -0,0 +1,13 @@
1
+ export function translateTools(tools) {
2
+ return tools.map((tool) => ({
3
+ type: 'function',
4
+ function: {
5
+ name: tool.function.name,
6
+ description: tool.function.description,
7
+ parameters: tool.function.parameters,
8
+ },
9
+ }));
10
+ }
11
+ export function translateToolResult(toolCallId, result) {
12
+ return `[tool result for ${toolCallId}]\n${result}`;
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { WindsurfConfig } from './types.js';
2
+ export declare function getToken(config: WindsurfConfig): string | null;
3
+ export declare function validateToken(token: string): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ const WINDSURF_API_BASE = 'https://server.codeium.com';
2
+ export function getToken(config) {
3
+ return config.WINDSURF_TOKEN ?? config.WINDSURF_OAUTH_TOKEN ?? null;
4
+ }
5
+ export async function validateToken(token) {
6
+ try {
7
+ const response = await fetch(`${WINDSURF_API_BASE}/api/v1/validate_token`, {
8
+ method: 'POST',
9
+ headers: {
10
+ 'Content-Type': 'application/json',
11
+ Authorization: `Bearer ${token}`,
12
+ },
13
+ body: JSON.stringify({ token }),
14
+ });
15
+ return response.ok;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
@@ -0,0 +1,6 @@
1
+ import { type DaemonManager } from '../../core/index.js';
2
+ interface WindsurfDaemonOptions {
3
+ knownPaths?: string[];
4
+ }
5
+ export declare function createWindsurfDaemon(options?: WindsurfDaemonOptions): DaemonManager;
6
+ export {};
@@ -0,0 +1,16 @@
1
+ import { createDaemonManager } from '../../core/index.js';
2
+ import { join } from 'node:path';
3
+ const DEFAULT_KNOWN_PATHS = [
4
+ join('/Applications', 'Windsurf.app', 'Contents', 'Resources', 'language_server'),
5
+ join('/usr', 'local', 'bin', 'language_server'),
6
+ ];
7
+ export function createWindsurfDaemon(options = {}) {
8
+ const knownPaths = options.knownPaths ?? DEFAULT_KNOWN_PATHS;
9
+ return createDaemonManager({
10
+ binaryName: 'language_server',
11
+ downloadUrl: `https://server.codeium.com/language_server/latest/{platform}/{arch}`,
12
+ checksum: 'PLACEHOLDER_SHA256',
13
+ knownPaths,
14
+ envVar: 'WINDSURF_LANGUAGE_SERVER_PATH',
15
+ });
16
+ }
@@ -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,4 @@
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';
@@ -0,0 +1,2 @@
1
+ import type { WindsurfModel } from './types.js';
2
+ export declare const WINDSURF_MODELS: WindsurfModel[];
@@ -0,0 +1,42 @@
1
+ export const WINDSURF_MODELS = [
2
+ {
3
+ id: 'claude-4.5-sonnet',
4
+ name: 'Claude 4.5 Sonnet (Windsurf)',
5
+ capabilities: { streaming: true, tools: true },
6
+ },
7
+ {
8
+ id: 'claude-4.5-opus',
9
+ name: 'Claude 4.5 Opus (Windsurf)',
10
+ capabilities: { streaming: true, tools: true },
11
+ },
12
+ {
13
+ id: 'gpt-5.2',
14
+ name: 'GPT-5.2 (Windsurf)',
15
+ capabilities: { streaming: true, tools: true },
16
+ },
17
+ {
18
+ id: 'gpt-5.2-codex',
19
+ name: 'GPT-5.2 Codex (Windsurf)',
20
+ capabilities: { streaming: true, tools: true },
21
+ },
22
+ {
23
+ id: 'gpt-4o',
24
+ name: 'GPT-4o (Windsurf)',
25
+ capabilities: { streaming: true, tools: true },
26
+ },
27
+ {
28
+ id: 'gemini-3.0-pro',
29
+ name: 'Gemini 3.0 Pro (Windsurf)',
30
+ capabilities: { streaming: true, tools: true },
31
+ },
32
+ {
33
+ id: 'gemini-3.0-flash',
34
+ name: 'Gemini 3.0 Flash (Windsurf)',
35
+ capabilities: { streaming: true, tools: true },
36
+ },
37
+ {
38
+ id: 'swe-1.5',
39
+ name: 'SWE-1.5 (Windsurf)',
40
+ capabilities: { streaming: true, tools: true },
41
+ },
42
+ ];
@@ -0,0 +1,8 @@
1
+ import type { BridgePlugin, BridgeSession, ModelInfo } from '../../core/index.js';
2
+ export declare class WindsurfBridgePlugin implements BridgePlugin {
3
+ name: string;
4
+ version: string;
5
+ authenticate(config: Record<string, string>): Promise<boolean>;
6
+ listModels(config: Record<string, string>): Promise<ModelInfo[]>;
7
+ createSession(config: Record<string, string>, model: string): Promise<BridgeSession>;
8
+ }
@@ -0,0 +1,31 @@
1
+ import { WindsurfBridgeSession } from './session.js';
2
+ import { WINDSURF_MODELS } from './models.js';
3
+ import { getToken, validateToken } from './auth.js';
4
+ import { createWindsurfDaemon } from './daemon.js';
5
+ export class WindsurfBridgePlugin {
6
+ name = 'windsurf';
7
+ version = '2.0.0';
8
+ async authenticate(config) {
9
+ const token = getToken(config);
10
+ if (!token)
11
+ return false;
12
+ return validateToken(token);
13
+ }
14
+ async listModels(config) {
15
+ const token = getToken(config);
16
+ if (!token)
17
+ throw new Error('Missing WINDSURF_TOKEN');
18
+ return WINDSURF_MODELS.map((m) => ({
19
+ id: m.id,
20
+ name: m.name,
21
+ capabilities: m.capabilities,
22
+ }));
23
+ }
24
+ async createSession(config, model) {
25
+ const token = getToken(config);
26
+ if (!token)
27
+ throw new Error('Missing WINDSURF_TOKEN');
28
+ const daemon = createWindsurfDaemon();
29
+ return new WindsurfBridgeSession(daemon, token, model);
30
+ }
31
+ }
@@ -0,0 +1,5 @@
1
+ import { DaemonBridgeSession } from '../../core/index.js';
2
+ import type { DaemonManager } from '../../core/index.js';
3
+ export declare class WindsurfBridgeSession extends DaemonBridgeSession {
4
+ constructor(daemon: DaemonManager, token: string, model: string, cwd?: string);
5
+ }
@@ -0,0 +1,6 @@
1
+ import { DaemonBridgeSession } from '../../core/index.js';
2
+ export class WindsurfBridgeSession extends DaemonBridgeSession {
3
+ constructor(daemon, token, model, cwd = process.cwd()) {
4
+ super(daemon, token, model, cwd);
5
+ }
6
+ }
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+ import type { WindsurfTool } from './types.js';
3
+ export declare function translateTools(tools: ToolDefinition[]): WindsurfTool[];
@@ -0,0 +1,10 @@
1
+ export function translateTools(tools) {
2
+ return tools.map((tool) => ({
3
+ type: 'function',
4
+ function: {
5
+ name: tool.function.name,
6
+ description: tool.function.description,
7
+ parameters: tool.function.parameters,
8
+ },
9
+ }));
10
+ }
@@ -0,0 +1,22 @@
1
+ export interface WindsurfModel {
2
+ id: string;
3
+ name: string;
4
+ capabilities: {
5
+ streaming: boolean;
6
+ tools: boolean;
7
+ vision?: boolean;
8
+ };
9
+ }
10
+ export interface WindsurfConfig {
11
+ WINDSURF_TOKEN?: string;
12
+ WINDSURF_OAUTH_TOKEN?: string;
13
+ WINDSURF_LANGUAGE_SERVER_PATH?: string;
14
+ }
15
+ export interface WindsurfTool {
16
+ type: 'function';
17
+ function: {
18
+ name: string;
19
+ description?: string;
20
+ parameters: Record<string, unknown>;
21
+ };
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { BridgeConfig } from '@ai-ide-bridge/core';
1
+ import { BridgeConfig } from '../core/index.js';
2
2
  export declare function readConfig(): BridgeConfig;
3
3
  export declare function writeConfig(config: BridgeConfig): void;
4
4
  export declare function setPluginConfig(pluginName: string, envVars: Record<string, string>): void;
@@ -1,4 +1,4 @@
1
- import { loadConfig, saveConfig } from '@ai-ide-bridge/core';
1
+ import { loadConfig, saveConfig } from '../core/index.js';
2
2
  export function readConfig() {
3
3
  return loadConfig();
4
4
  }
@@ -0,0 +1 @@
1
+ export declare function getPlatform(): NodeJS.Platform;
@@ -0,0 +1,3 @@
1
+ export function getPlatform() {
2
+ return process.platform;
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ide-bridge/cli",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "llm-bridge": "./dist/index.js"
@@ -14,10 +14,8 @@
14
14
  "start": "node dist/index.js"
15
15
  },
16
16
  "dependencies": {
17
- "@ai-ide-bridge/copilot": "workspace:*",
18
- "@ai-ide-bridge/core": "workspace:*",
19
- "@ai-ide-bridge/cursor": "workspace:*",
20
- "@ai-ide-bridge/windsurf": "workspace:*"
17
+ "@cursor/sdk": "^1.0.13",
18
+ "zod": "^3.25.76"
21
19
  },
22
20
  "devDependencies": {
23
21
  "@types/node": "^22.15.0",
@@ -2,20 +2,31 @@ 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';
5
+ import { createWindsurfDaemon } from '../plugins/windsurf/daemon.js';
6
+ import { getPlatform } from '../utils/platform.js';
6
7
 
7
- const __filename = fileURLToPath(import.meta.url);
8
- const __dirname = path.dirname(__filename);
9
8
  const LABEL = 'com.llm-bridge.daemon';
10
9
 
11
10
  export async function installDaemonCommand(): Promise<void> {
12
- if (process.platform !== 'darwin') {
13
- console.error('Daemon installation is only supported on macOS.');
11
+ const platform = getPlatform();
12
+ if (platform === 'darwin') {
13
+ await installMacOSDaemon();
14
+ } else if (platform === 'linux') {
15
+ await installLinuxDaemon();
16
+ } else {
17
+ console.error('Daemon installation is only supported on macOS and Linux.');
14
18
  process.exit(1);
15
19
  }
20
+ }
16
21
 
22
+ async function installMacOSDaemon(): Promise<void> {
17
23
  const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
18
- const wrapperPath = path.join(__dirname, '..', '..', 'scripts', 'llm-bridge-daemon.sh');
24
+ const wrapperPath = path.join(
25
+ path.dirname(process.execPath),
26
+ '..',
27
+ 'scripts',
28
+ 'llm-bridge-daemon.sh',
29
+ );
19
30
 
20
31
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
21
32
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -40,8 +51,13 @@ export async function installDaemonCommand(): Promise<void> {
40
51
  </dict>
41
52
  </plist>`;
42
53
 
43
- fs.mkdirSync(path.dirname(plistPath), { recursive: true });
44
- fs.writeFileSync(plistPath, plist);
54
+ try {
55
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
56
+ fs.writeFileSync(plistPath, plist);
57
+ } catch (e) {
58
+ console.error('Failed to write plist file:', e);
59
+ process.exit(1);
60
+ }
45
61
 
46
62
  try {
47
63
  execSync(`launchctl bootstrap "gui/$(id -u)" "${plistPath}"`, { stdio: 'inherit' });
@@ -53,12 +69,58 @@ export async function installDaemonCommand(): Promise<void> {
53
69
  }
54
70
  }
55
71
 
72
+ async function installLinuxDaemon(): Promise<void> {
73
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user');
74
+ const servicePath = path.join(serviceDir, 'llm-bridge.service');
75
+
76
+ const binaryPath = process.execPath;
77
+
78
+ const unit = `[Unit]
79
+ Description=llm-bridge daemon
80
+ After=network.target
81
+
82
+ [Service]
83
+ Type=simple
84
+ ExecStart=${binaryPath} start
85
+ Restart=on-failure
86
+ RestartSec=5
87
+
88
+ [Install]
89
+ WantedBy=default.target
90
+ `;
91
+
92
+ try {
93
+ fs.mkdirSync(serviceDir, { recursive: true });
94
+ fs.writeFileSync(servicePath, unit);
95
+ } catch (e) {
96
+ console.error('Failed to write service file:', e);
97
+ process.exit(1);
98
+ }
99
+
100
+ try {
101
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
102
+ execSync('systemctl --user enable --now llm-bridge', { stdio: 'inherit' });
103
+ console.log(`Installed systemd user service: ${servicePath}`);
104
+ console.log('Logs: journalctl --user -u llm-bridge -f');
105
+ } catch (e) {
106
+ console.error('Failed to enable systemd service:', e);
107
+ process.exit(1);
108
+ }
109
+ }
110
+
56
111
  export async function uninstallDaemonCommand(): Promise<void> {
57
- if (process.platform !== 'darwin') {
58
- console.error('Daemon uninstallation is only supported on macOS.');
112
+ const platform = getPlatform();
113
+ if (platform === 'darwin') {
114
+ await uninstallMacOSDaemon();
115
+ } else if (platform === 'linux') {
116
+ await uninstallLinuxDaemon();
117
+ } else {
118
+ console.error('Daemon uninstallation is only supported on macOS and Linux.');
59
119
  process.exit(1);
60
120
  }
121
+ }
61
122
 
123
+ async function uninstallMacOSDaemon(): Promise<void> {
62
124
  const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
63
125
 
64
126
  try {
@@ -77,8 +139,32 @@ export async function uninstallDaemonCommand(): Promise<void> {
77
139
  }
78
140
  }
79
141
 
142
+ async function uninstallLinuxDaemon(): Promise<void> {
143
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'llm-bridge.service');
144
+
145
+ try {
146
+ execSync('systemctl --user disable --now llm-bridge 2>/dev/null || true', {
147
+ stdio: 'inherit',
148
+ });
149
+ } catch {
150
+ // Ignore errors during disable
151
+ }
152
+
153
+ if (fs.existsSync(servicePath)) {
154
+ fs.unlinkSync(servicePath);
155
+ console.log(`Removed systemd service: ${servicePath}`);
156
+ } else {
157
+ console.log('No systemd service found.');
158
+ }
159
+
160
+ try {
161
+ execSync('systemctl --user daemon-reload', { stdio: 'inherit' });
162
+ } catch {
163
+ // Ignore errors during reload
164
+ }
165
+ }
166
+
80
167
  export async function daemonStatusCommand(): Promise<void> {
81
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
82
168
  const daemon = createWindsurfDaemon();
83
169
 
84
170
  const path = await daemon.locate();
@@ -93,7 +179,6 @@ export async function daemonStatusCommand(): Promise<void> {
93
179
  }
94
180
 
95
181
  export async function daemonDownloadCommand(): Promise<void> {
96
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
97
182
  const daemon = createWindsurfDaemon();
98
183
 
99
184
  console.log('Downloading Windsurf language server...');
@@ -107,7 +192,6 @@ export async function daemonDownloadCommand(): Promise<void> {
107
192
  }
108
193
 
109
194
  export async function daemonLocateCommand(): Promise<void> {
110
- const { createWindsurfDaemon } = await import('@ai-ide-bridge/windsurf/daemon.js');
111
195
  const daemon = createWindsurfDaemon();
112
196
 
113
197
  const path = await daemon.locate();
@@ -118,3 +202,18 @@ export async function daemonLocateCommand(): Promise<void> {
118
202
  process.exit(1);
119
203
  }
120
204
  }
205
+
206
+ export async function daemonReloadCommand(): Promise<void> {
207
+ if (getPlatform() !== 'linux') {
208
+ console.error('Daemon reload is only supported on Linux.');
209
+ process.exit(1);
210
+ }
211
+
212
+ try {
213
+ execSync('systemctl --user reload-or-restart llm-bridge', { stdio: 'inherit' });
214
+ console.log('Daemon reloaded.');
215
+ } catch (e) {
216
+ console.error('Failed to reload daemon:', e);
217
+ process.exit(1);
218
+ }
219
+ }
@@ -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
 
6
6
  export async function doctorCommand(): Promise<void> {
7
7
  const config = readConfig();
@@ -1,8 +1,9 @@
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 { loginCommand } from './login.js';
6
7
 
7
8
  const PROVIDERS = ['cursor', 'copilot', 'windsurf'] as const;
8
9
  type Provider = (typeof PROVIDERS)[number];
@@ -23,7 +24,7 @@ function getCredentialPrompt(provider: Provider): string {
23
24
  case 'cursor':
24
25
  return 'Enter your CURSOR_API_KEY: ';
25
26
  case 'copilot':
26
- return 'Enter your GITHUB_TOKEN: ';
27
+ return 'Enter your GITHUB_TOKEN (or leave empty for OAuth): ';
27
28
  case 'windsurf':
28
29
  return 'Enter your WINDSURF_TOKEN: ';
29
30
  }
@@ -40,6 +41,15 @@ function getEnvVar(provider: Provider): string {
40
41
  }
41
42
  }
42
43
 
44
+ async function authenticateWithOAuth(provider: Provider): Promise<boolean> {
45
+ if (provider === 'copilot') {
46
+ await loginCommand('copilot');
47
+ return true;
48
+ }
49
+ console.log(`OAuth not available for ${provider}. Use token authentication instead.`);
50
+ return false;
51
+ }
52
+
43
53
  export async function initCommand(): Promise<void> {
44
54
  const rl = createInterface({ input: process.stdin, output: process.stdout });
45
55
  const ask = (q: string) => new Promise<string>((resolve) => rl.question(q, resolve));
@@ -69,6 +79,21 @@ export async function initCommand(): Promise<void> {
69
79
 
70
80
  const provider = input as Provider;
71
81
 
82
+ if (provider === 'copilot') {
83
+ const method = await ask('Auth method (token/oauth): ');
84
+ if (method.toLowerCase() === 'oauth') {
85
+ const ok = await authenticateWithOAuth(provider);
86
+ if (!ok) continue;
87
+ console.log(`Authentication successful for ${provider}.`);
88
+ setPluginConfig(provider, { COPILOT_OAUTH: 'true' });
89
+ configuredProviders.push(provider);
90
+ if (!firstProvider) firstProvider = provider;
91
+ const more = await ask('Configure another provider? (y/n): ');
92
+ if (more.toLowerCase() !== 'y') break;
93
+ continue;
94
+ }
95
+ }
96
+
72
97
  const envVar = getEnvVar(provider);
73
98
  const credential = await ask(getCredentialPrompt(provider));
74
99
  if (!credential) {