@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/commands/daemon.d.ts +1 -0
- package/dist/commands/daemon.js +107 -13
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/init.js +30 -4
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +62 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/start.js +4 -4
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +43 -0
- package/dist/core/daemon-session.d.ts +14 -0
- package/dist/core/daemon-session.js +179 -0
- package/dist/core/daemon.d.ts +16 -0
- package/dist/core/daemon.js +168 -0
- package/dist/core/formatter.d.ts +3 -0
- package/dist/core/formatter.js +44 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.js +9 -0
- package/dist/core/parser.d.ts +164 -0
- package/dist/core/parser.js +37 -0
- package/dist/core/registry.d.ts +16 -0
- package/dist/core/registry.js +53 -0
- package/dist/core/server.d.ts +19 -0
- package/dist/core/server.js +185 -0
- package/dist/core/session.d.ts +11 -0
- package/dist/core/session.js +39 -0
- package/dist/core/types.d.ts +166 -0
- package/dist/core/types.js +44 -0
- package/dist/index.js +22 -5
- package/dist/oauth/device-flow.d.ts +12 -0
- package/dist/oauth/device-flow.js +93 -0
- package/dist/oauth/flow.d.ts +11 -0
- package/dist/oauth/flow.js +75 -0
- package/dist/oauth/index.d.ts +6 -0
- package/dist/oauth/index.js +5 -0
- package/dist/oauth/lifecycle.d.ts +13 -0
- package/dist/oauth/lifecycle.js +56 -0
- package/dist/oauth/providers.d.ts +2 -0
- package/dist/oauth/providers.js +19 -0
- package/dist/oauth/storage-file.d.ts +2 -0
- package/dist/oauth/storage-file.js +68 -0
- package/dist/oauth/storage.d.ts +2 -0
- package/dist/oauth/storage.js +4 -0
- package/dist/oauth/types.d.ts +44 -0
- package/dist/oauth/types.js +1 -0
- package/dist/plugins/copilot/auth.d.ts +7 -0
- package/dist/plugins/copilot/auth.js +30 -0
- package/dist/plugins/copilot/index.d.ts +5 -0
- package/dist/plugins/copilot/index.js +4 -0
- package/dist/plugins/copilot/plugin.d.ts +8 -0
- package/dist/plugins/copilot/plugin.js +29 -0
- package/dist/plugins/copilot/session.d.ts +8 -0
- package/dist/plugins/copilot/session.js +115 -0
- package/dist/plugins/copilot/tools.d.ts +10 -0
- package/dist/plugins/copilot/tools.js +10 -0
- package/dist/plugins/copilot/types.d.ts +15 -0
- package/dist/plugins/copilot/types.js +27 -0
- package/dist/plugins/cursor/index.d.ts +2 -0
- package/dist/plugins/cursor/index.js +2 -0
- package/dist/plugins/cursor/plugin.d.ts +8 -0
- package/dist/plugins/cursor/plugin.js +36 -0
- package/dist/plugins/cursor/session.d.ts +11 -0
- package/dist/plugins/cursor/session.js +69 -0
- package/dist/plugins/cursor/tools.d.ts +11 -0
- package/dist/plugins/cursor/tools.js +13 -0
- package/dist/plugins/windsurf/auth.d.ts +3 -0
- package/dist/plugins/windsurf/auth.js +20 -0
- package/dist/plugins/windsurf/daemon.d.ts +6 -0
- package/dist/plugins/windsurf/daemon.js +16 -0
- package/dist/plugins/windsurf/index.d.ts +5 -0
- package/dist/plugins/windsurf/index.js +4 -0
- package/dist/plugins/windsurf/models.d.ts +2 -0
- package/dist/plugins/windsurf/models.js +42 -0
- package/dist/plugins/windsurf/plugin.d.ts +8 -0
- package/dist/plugins/windsurf/plugin.js +31 -0
- package/dist/plugins/windsurf/session.d.ts +5 -0
- package/dist/plugins/windsurf/session.js +6 -0
- package/dist/plugins/windsurf/tools.d.ts +3 -0
- package/dist/plugins/windsurf/tools.js +10 -0
- package/dist/plugins/windsurf/types.d.ts +22 -0
- package/dist/plugins/windsurf/types.js +1 -0
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +1 -1
- package/dist/utils/platform.d.ts +1 -0
- package/dist/utils/platform.js +3 -0
- package/package.json +3 -5
- package/src/commands/daemon.ts +112 -13
- package/src/commands/doctor.ts +1 -1
- package/src/commands/init.ts +29 -4
- package/src/commands/login.ts +98 -0
- package/src/commands/logout.ts +15 -0
- package/src/commands/start.ts +4 -4
- package/src/core/config.ts +45 -0
- package/src/core/daemon-session.ts +199 -0
- package/src/core/daemon.ts +206 -0
- package/src/core/formatter.ts +56 -0
- package/src/core/index.ts +9 -0
- package/src/core/parser.ts +47 -0
- package/src/core/registry.ts +62 -0
- package/src/core/server.ts +211 -0
- package/src/core/session.ts +54 -0
- package/src/core/types.ts +100 -0
- package/src/index.ts +22 -4
- package/src/oauth/device-flow.ts +111 -0
- package/src/oauth/flow.ts +94 -0
- package/src/oauth/index.ts +6 -0
- package/src/oauth/lifecycle.ts +77 -0
- package/src/oauth/providers.ts +21 -0
- package/src/oauth/storage-file.ts +77 -0
- package/src/oauth/storage.ts +6 -0
- package/src/oauth/types.ts +50 -0
- package/src/plugins/copilot/auth.ts +39 -0
- package/src/plugins/copilot/index.ts +5 -0
- package/src/plugins/copilot/plugin.ts +31 -0
- package/src/plugins/copilot/session.ts +130 -0
- package/src/plugins/copilot/tools.ts +21 -0
- package/src/plugins/copilot/types.ts +43 -0
- package/src/plugins/cursor/index.ts +2 -0
- package/src/plugins/cursor/plugin.ts +37 -0
- package/src/plugins/cursor/session.ts +78 -0
- package/src/plugins/cursor/tools.ts +25 -0
- package/src/plugins/windsurf/auth.ts +23 -0
- package/src/plugins/windsurf/daemon.ts +24 -0
- package/src/plugins/windsurf/index.ts +5 -0
- package/src/plugins/windsurf/models.ts +44 -0
- package/src/plugins/windsurf/plugin.ts +34 -0
- package/src/plugins/windsurf/session.ts +8 -0
- package/src/plugins/windsurf/tools.ts +13 -0
- package/src/plugins/windsurf/types.ts +24 -0
- package/src/utils/config.ts +1 -1
- package/src/utils/platform.ts +3 -0
- 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,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,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,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,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 {};
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BridgeConfig } from '
|
|
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;
|
package/dist/utils/config.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getPlatform(): NodeJS.Platform;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ide-bridge/cli",
|
|
3
|
-
"version": "1.0
|
|
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
|
-
"@
|
|
18
|
-
"
|
|
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",
|
package/src/commands/daemon.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
13
|
-
|
|
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(
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
+
}
|
package/src/commands/doctor.ts
CHANGED
|
@@ -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 '
|
|
4
|
+
import { configPath } from '../core/index.js';
|
|
5
5
|
|
|
6
6
|
export async function doctorCommand(): Promise<void> {
|
|
7
7
|
const config = readConfig();
|
package/src/commands/init.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { setPluginConfig, writeConfig, readConfig } from '../utils/config.js';
|
|
2
|
-
import { CursorBridgePlugin } from '
|
|
3
|
-
import { CopilotBridgePlugin } from '
|
|
4
|
-
import { WindsurfBridgePlugin } from '
|
|
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) {
|