osborn 0.1.1 ā 0.1.6
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/dist/claude-handler.d.ts +19 -2
- package/dist/claude-handler.js +160 -34
- package/dist/config.d.ts +33 -0
- package/dist/config.js +127 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +511 -309
- package/package.json +7 -4
package/dist/claude-handler.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { EventEmitter } from 'events';
|
|
|
3
3
|
interface ClaudeHandlerOptions {
|
|
4
4
|
workingDirectory?: string;
|
|
5
5
|
allowedTools?: string[];
|
|
6
|
-
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions';
|
|
6
|
+
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
|
|
7
7
|
mcpServers?: Record<string, McpServerConfig>;
|
|
8
|
+
requireAllPermissions?: boolean;
|
|
9
|
+
agentRole?: 'plan' | 'execute';
|
|
8
10
|
}
|
|
9
11
|
export type { McpServerConfig };
|
|
10
12
|
export interface PermissionRequestEvent {
|
|
@@ -28,10 +30,25 @@ export declare class ClaudeHandler extends EventEmitter {
|
|
|
28
30
|
private abortController;
|
|
29
31
|
private sessionId;
|
|
30
32
|
private pendingPermission;
|
|
31
|
-
private
|
|
33
|
+
private toolStartTimes;
|
|
32
34
|
private alwaysAllowedTools;
|
|
33
35
|
private static readonly ALL_TOOLS;
|
|
36
|
+
private static readonly PLAN_TOOLS;
|
|
37
|
+
private static readonly EXECUTE_TOOLS;
|
|
38
|
+
private agentRole;
|
|
34
39
|
constructor(options?: ClaudeHandlerOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Get the agent's role
|
|
42
|
+
*/
|
|
43
|
+
getRole(): 'plan' | 'execute';
|
|
44
|
+
/**
|
|
45
|
+
* Check if this is a plan-mode agent
|
|
46
|
+
*/
|
|
47
|
+
isPlanMode(): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Generate human-readable description for a tool call
|
|
50
|
+
*/
|
|
51
|
+
private getToolDescription;
|
|
35
52
|
run(prompt: string): Promise<string>;
|
|
36
53
|
/**
|
|
37
54
|
* Request permission from user via event emission
|
package/dist/claude-handler.js
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
|
+
// Log tool calls to terminal (async, non-blocking)
|
|
4
|
+
function logToolCall(entry) {
|
|
5
|
+
// Use setImmediate to avoid blocking the main execution
|
|
6
|
+
setImmediate(() => {
|
|
7
|
+
const time = new Date().toLocaleTimeString();
|
|
8
|
+
const inputStr = JSON.stringify(entry.input).substring(0, 100);
|
|
9
|
+
switch (entry.status) {
|
|
10
|
+
case 'started':
|
|
11
|
+
console.log(`\nš§ [${time}] TOOL START: ${entry.toolName}`);
|
|
12
|
+
console.log(` š„ Input: ${inputStr}${inputStr.length >= 100 ? '...' : ''}`);
|
|
13
|
+
break;
|
|
14
|
+
case 'completed':
|
|
15
|
+
const duration = entry.duration ? `${entry.duration}ms` : '?';
|
|
16
|
+
console.log(`ā
[${time}] TOOL DONE: ${entry.toolName} (${duration})`);
|
|
17
|
+
if (entry.output) {
|
|
18
|
+
const outStr = typeof entry.output === 'string'
|
|
19
|
+
? entry.output.substring(0, 150)
|
|
20
|
+
: JSON.stringify(entry.output).substring(0, 150);
|
|
21
|
+
console.log(` š¤ Output: ${outStr}${outStr.length >= 150 ? '...' : ''}`);
|
|
22
|
+
}
|
|
23
|
+
break;
|
|
24
|
+
case 'blocked':
|
|
25
|
+
console.log(`ā [${time}] TOOL BLOCKED: ${entry.toolName}`);
|
|
26
|
+
console.log(` ā Reason: ${entry.error || 'User denied'}`);
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
3
31
|
export class ClaudeHandler extends EventEmitter {
|
|
4
32
|
options;
|
|
5
33
|
abortController = null;
|
|
6
34
|
sessionId = null;
|
|
7
35
|
pendingPermission = null;
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// Tools the user has permanently approved
|
|
36
|
+
// Track tool call start times for duration logging
|
|
37
|
+
toolStartTimes = new Map();
|
|
38
|
+
// Tools the user has permanently approved (for this session)
|
|
11
39
|
alwaysAllowedTools = new Set();
|
|
12
40
|
// All available Claude Agent SDK tools
|
|
13
41
|
static ALL_TOOLS = [
|
|
@@ -26,19 +54,94 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
26
54
|
// LSP (Language Server Protocol)
|
|
27
55
|
'LSP',
|
|
28
56
|
];
|
|
57
|
+
// Plan mode tools - read-only, research, context gathering
|
|
58
|
+
static PLAN_TOOLS = [
|
|
59
|
+
'Read', // View file contents
|
|
60
|
+
'Glob', // File pattern matching
|
|
61
|
+
'Grep', // Content searching
|
|
62
|
+
'Bash', // Read-only bash (ls, git status, git log, etc.)
|
|
63
|
+
'Task', // Research agents
|
|
64
|
+
'WebFetch', // Web content analysis
|
|
65
|
+
'WebSearch', // Internet searching
|
|
66
|
+
'LSP', // Code intelligence (go to definition, references)
|
|
67
|
+
];
|
|
68
|
+
// Execute mode tools - full access
|
|
69
|
+
static EXECUTE_TOOLS = ClaudeHandler.ALL_TOOLS;
|
|
70
|
+
agentRole;
|
|
29
71
|
constructor(options = {}) {
|
|
30
72
|
super();
|
|
73
|
+
// Set agent role
|
|
74
|
+
this.agentRole = options.agentRole || (options.permissionMode === 'plan' ? 'plan' : 'execute');
|
|
75
|
+
// For plan mode, restrict to read-only tools
|
|
76
|
+
const isPlanMode = options.permissionMode === 'plan';
|
|
77
|
+
const defaultTools = isPlanMode ? ClaudeHandler.PLAN_TOOLS : ClaudeHandler.ALL_TOOLS;
|
|
31
78
|
this.options = {
|
|
32
79
|
workingDirectory: options.workingDirectory || process.cwd(),
|
|
33
|
-
allowedTools: options.allowedTools ||
|
|
34
|
-
|
|
80
|
+
allowedTools: options.allowedTools || defaultTools,
|
|
81
|
+
// Plan mode uses 'default' permission mode but with restricted tools
|
|
82
|
+
permissionMode: isPlanMode ? 'default' : (options.permissionMode || 'default'),
|
|
35
83
|
mcpServers: options.mcpServers,
|
|
84
|
+
// Plan mode doesn't require permissions (read-only is safe)
|
|
85
|
+
// Execute mode requires permissions for safety
|
|
86
|
+
requireAllPermissions: isPlanMode ? false : (options.requireAllPermissions ?? true),
|
|
36
87
|
};
|
|
88
|
+
const roleEmoji = this.agentRole === 'plan' ? 'š' : 'šØ';
|
|
89
|
+
console.log(`${roleEmoji} Agent role: ${this.agentRole.toUpperCase()}`);
|
|
37
90
|
console.log(`š§ Allowed tools: ${this.options.allowedTools?.join(', ')}`);
|
|
91
|
+
console.log(`š Require permissions: ${this.options.requireAllPermissions}`);
|
|
38
92
|
if (this.options.mcpServers) {
|
|
39
93
|
console.log(`š MCP servers: ${Object.keys(this.options.mcpServers).join(', ')}`);
|
|
40
94
|
}
|
|
41
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Get the agent's role
|
|
98
|
+
*/
|
|
99
|
+
getRole() {
|
|
100
|
+
return this.agentRole;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if this is a plan-mode agent
|
|
104
|
+
*/
|
|
105
|
+
isPlanMode() {
|
|
106
|
+
return this.agentRole === 'plan';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Generate human-readable description for a tool call
|
|
110
|
+
*/
|
|
111
|
+
getToolDescription(toolName, toolInput) {
|
|
112
|
+
switch (toolName) {
|
|
113
|
+
case 'Bash':
|
|
114
|
+
return `Run command: ${toolInput.command || 'unknown command'}`;
|
|
115
|
+
case 'Write':
|
|
116
|
+
return `Create file: ${toolInput.file_path || 'unknown file'}`;
|
|
117
|
+
case 'Edit':
|
|
118
|
+
return `Edit file: ${toolInput.file_path || 'unknown file'}`;
|
|
119
|
+
case 'MultiEdit':
|
|
120
|
+
return `Multi-edit: ${toolInput.edits?.length || 0} edits`;
|
|
121
|
+
case 'Read':
|
|
122
|
+
return `Read file: ${toolInput.file_path || 'unknown file'}`;
|
|
123
|
+
case 'Glob':
|
|
124
|
+
return `Find files: ${toolInput.pattern || 'unknown pattern'}`;
|
|
125
|
+
case 'Grep':
|
|
126
|
+
return `Search content: "${toolInput.pattern || 'unknown'}" in ${toolInput.path || 'cwd'}`;
|
|
127
|
+
case 'WebSearch':
|
|
128
|
+
return `š Web search: "${toolInput.query || 'unknown query'}"`;
|
|
129
|
+
case 'WebFetch':
|
|
130
|
+
return `š Fetch URL: ${toolInput.url || 'unknown url'}`;
|
|
131
|
+
case 'NotebookEdit':
|
|
132
|
+
return `Edit notebook: ${toolInput.notebook_path || 'unknown'}`;
|
|
133
|
+
case 'Task':
|
|
134
|
+
return `Spawn task: ${toolInput.description || 'unknown task'}`;
|
|
135
|
+
case 'TodoWrite':
|
|
136
|
+
return `Update todos: ${toolInput.todos?.length || 0} items`;
|
|
137
|
+
case 'LSP':
|
|
138
|
+
return `LSP ${toolInput.operation || 'query'}: ${toolInput.filePath || 'unknown'}`;
|
|
139
|
+
default:
|
|
140
|
+
// For MCP tools, show the tool name and first few input keys
|
|
141
|
+
const inputKeys = Object.keys(toolInput || {}).slice(0, 3).join(', ');
|
|
142
|
+
return `${toolName}: ${inputKeys || 'no params'}`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
42
145
|
async run(prompt) {
|
|
43
146
|
this.abortController = new AbortController();
|
|
44
147
|
let fullResponse = '';
|
|
@@ -68,10 +171,26 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
68
171
|
hooks: [async (input, toolUseId) => {
|
|
69
172
|
const toolName = input?.tool_name || 'unknown';
|
|
70
173
|
const toolInput = input?.tool_input || {};
|
|
174
|
+
const id = toolUseId || `tool-${Date.now()}`;
|
|
175
|
+
const description = this.getToolDescription(toolName, toolInput);
|
|
176
|
+
// Record start time for duration tracking
|
|
177
|
+
this.toolStartTimes.set(id, Date.now());
|
|
178
|
+
// Log tool start (background, non-blocking)
|
|
179
|
+
logToolCall({
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
toolName,
|
|
182
|
+
toolUseId: id,
|
|
183
|
+
input: toolInput,
|
|
184
|
+
status: 'started',
|
|
185
|
+
});
|
|
71
186
|
console.log(`š§ Tool: ${toolName}`);
|
|
72
|
-
|
|
187
|
+
console.log(` š ${description}`);
|
|
188
|
+
this.emit('tool_use', { name: toolName, input: toolInput, description });
|
|
73
189
|
// Check if this tool needs permission
|
|
74
|
-
|
|
190
|
+
// In default mode with requireAllPermissions, ALL tools need permission
|
|
191
|
+
const needsPermission = this.options.permissionMode === 'default' &&
|
|
192
|
+
this.options.requireAllPermissions;
|
|
193
|
+
if (needsPermission) {
|
|
75
194
|
// Skip if user has permanently approved this tool
|
|
76
195
|
if (this.alwaysAllowedTools.has(toolName)) {
|
|
77
196
|
console.log(`ā
Auto-approved (always allow): ${toolName}`);
|
|
@@ -79,9 +198,18 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
79
198
|
else {
|
|
80
199
|
console.log(`ā ļø Permission required for: ${toolName}`);
|
|
81
200
|
// Emit permission request and wait for approval
|
|
82
|
-
const response = await this.requestPermission(toolName, toolInput,
|
|
201
|
+
const response = await this.requestPermission(toolName, toolInput, id, description);
|
|
83
202
|
if (response === 'deny') {
|
|
84
203
|
console.log(`ā Permission denied for: ${toolName}`);
|
|
204
|
+
// Log blocked tool
|
|
205
|
+
logToolCall({
|
|
206
|
+
timestamp: new Date().toISOString(),
|
|
207
|
+
toolName,
|
|
208
|
+
toolUseId: id,
|
|
209
|
+
input: toolInput,
|
|
210
|
+
status: 'blocked',
|
|
211
|
+
error: 'User denied permission',
|
|
212
|
+
});
|
|
85
213
|
return {
|
|
86
214
|
decision: 'block',
|
|
87
215
|
reason: 'User denied permission for this operation'
|
|
@@ -101,10 +229,26 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
101
229
|
}],
|
|
102
230
|
PostToolUse: [{
|
|
103
231
|
matcher: '.*',
|
|
104
|
-
hooks: [async (input) => {
|
|
232
|
+
hooks: [async (input, toolUseId) => {
|
|
105
233
|
const toolName = input?.tool_name || 'unknown';
|
|
106
|
-
|
|
107
|
-
|
|
234
|
+
const toolOutput = input?.tool_output || input?.output;
|
|
235
|
+
const id = toolUseId || 'unknown';
|
|
236
|
+
// Calculate duration
|
|
237
|
+
const startTime = this.toolStartTimes.get(id);
|
|
238
|
+
const duration = startTime ? Date.now() - startTime : undefined;
|
|
239
|
+
this.toolStartTimes.delete(id);
|
|
240
|
+
// Log tool completion (background, non-blocking)
|
|
241
|
+
logToolCall({
|
|
242
|
+
timestamp: new Date().toISOString(),
|
|
243
|
+
toolName,
|
|
244
|
+
toolUseId: id,
|
|
245
|
+
input: input?.tool_input || {},
|
|
246
|
+
status: 'completed',
|
|
247
|
+
output: toolOutput ? (typeof toolOutput === 'string' ? toolOutput.substring(0, 500) : 'object') : undefined,
|
|
248
|
+
duration,
|
|
249
|
+
});
|
|
250
|
+
console.log(`ā
Completed: ${toolName} (${duration ? duration + 'ms' : 'unknown duration'})`);
|
|
251
|
+
this.emit('tool_result', { name: toolName, output: toolOutput, duration });
|
|
108
252
|
return {};
|
|
109
253
|
}]
|
|
110
254
|
}]
|
|
@@ -168,24 +312,15 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
168
312
|
* Request permission from user via event emission
|
|
169
313
|
* Returns a promise that resolves when user responds with allow/deny/always_allow
|
|
170
314
|
*/
|
|
171
|
-
requestPermission(toolName, toolInput, toolUseId) {
|
|
315
|
+
requestPermission(toolName, toolInput, toolUseId, description) {
|
|
172
316
|
return new Promise((resolve) => {
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
if (toolName === 'Bash') {
|
|
176
|
-
description = `Run command: ${toolInput.command || 'unknown command'}`;
|
|
177
|
-
}
|
|
178
|
-
else if (toolName === 'Write') {
|
|
179
|
-
description = `Create file: ${toolInput.file_path || 'unknown file'}`;
|
|
180
|
-
}
|
|
181
|
-
else if (toolName === 'Edit') {
|
|
182
|
-
description = `Edit file: ${toolInput.file_path || 'unknown file'}`;
|
|
183
|
-
}
|
|
317
|
+
// Use provided description or generate one
|
|
318
|
+
const desc = description || this.getToolDescription(toolName, toolInput);
|
|
184
319
|
this.pendingPermission = { toolName, toolInput, toolUseId, resolve: resolve };
|
|
185
320
|
// Emit event for voice handler to pick up
|
|
186
321
|
this.emit('permission_request', {
|
|
187
322
|
toolName,
|
|
188
|
-
description,
|
|
323
|
+
description: desc,
|
|
189
324
|
toolInput,
|
|
190
325
|
toolUseId,
|
|
191
326
|
});
|
|
@@ -240,16 +375,7 @@ export class ClaudeHandler extends EventEmitter {
|
|
|
240
375
|
if (!this.pendingPermission)
|
|
241
376
|
return null;
|
|
242
377
|
const { toolName, toolInput, toolUseId } = this.pendingPermission;
|
|
243
|
-
|
|
244
|
-
if (toolName === 'Bash') {
|
|
245
|
-
description = `Run command: ${toolInput.command || 'unknown command'}`;
|
|
246
|
-
}
|
|
247
|
-
else if (toolName === 'Write') {
|
|
248
|
-
description = `Create file: ${toolInput.file_path || 'unknown file'}`;
|
|
249
|
-
}
|
|
250
|
-
else if (toolName === 'Edit') {
|
|
251
|
-
description = `Edit file: ${toolInput.file_path || 'unknown file'}`;
|
|
252
|
-
}
|
|
378
|
+
const description = this.getToolDescription(toolName, toolInput);
|
|
253
379
|
return { toolName, description, toolInput, toolUseId };
|
|
254
380
|
}
|
|
255
381
|
/**
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { McpServerConfig } from './claude-handler.js';
|
|
2
|
+
export interface OsbornConfig {
|
|
3
|
+
workingDirectory?: string;
|
|
4
|
+
mcpServers?: Record<string, McpServerConfigYaml>;
|
|
5
|
+
defaultProvider?: 'openai' | 'gemini';
|
|
6
|
+
defaultCodingAgent?: 'claude' | 'codex';
|
|
7
|
+
}
|
|
8
|
+
interface McpServerConfigYaml {
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
command?: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
url?: string;
|
|
14
|
+
transport?: 'stdio' | 'sse' | 'http';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Load configuration from ~/.osborn/config.yaml
|
|
18
|
+
* Creates default config if it doesn't exist
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadConfig(): OsbornConfig;
|
|
21
|
+
/**
|
|
22
|
+
* Get enabled MCP servers in the format expected by Claude Agent SDK
|
|
23
|
+
*/
|
|
24
|
+
export declare function getMcpServers(config: OsbornConfig): Record<string, McpServerConfig>;
|
|
25
|
+
/**
|
|
26
|
+
* Get list of enabled MCP server names (for display)
|
|
27
|
+
*/
|
|
28
|
+
export declare function getEnabledMcpServerNames(config: OsbornConfig): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Save config to file
|
|
31
|
+
*/
|
|
32
|
+
export declare function saveConfig(config: OsbornConfig): void;
|
|
33
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { parse, stringify } from 'yaml';
|
|
5
|
+
// Config file paths
|
|
6
|
+
const CONFIG_DIR = join(homedir(), '.osborn');
|
|
7
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.yaml');
|
|
8
|
+
// Default config template
|
|
9
|
+
const DEFAULT_CONFIG = {
|
|
10
|
+
workingDirectory: process.cwd(),
|
|
11
|
+
defaultProvider: 'openai',
|
|
12
|
+
defaultCodingAgent: 'claude',
|
|
13
|
+
mcpServers: {
|
|
14
|
+
// Example MCP servers (disabled by default)
|
|
15
|
+
// github: {
|
|
16
|
+
// enabled: true,
|
|
17
|
+
// command: 'npx',
|
|
18
|
+
// args: ['@modelcontextprotocol/server-github'],
|
|
19
|
+
// env: {
|
|
20
|
+
// GITHUB_TOKEN: '${GITHUB_TOKEN}',
|
|
21
|
+
// },
|
|
22
|
+
// },
|
|
23
|
+
// filesystem: {
|
|
24
|
+
// enabled: true,
|
|
25
|
+
// command: 'npx',
|
|
26
|
+
// args: ['@modelcontextprotocol/server-filesystem', '/allowed/path'],
|
|
27
|
+
// },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Resolve environment variable references in a string
|
|
32
|
+
* e.g., "${GITHUB_TOKEN}" becomes the value of process.env.GITHUB_TOKEN
|
|
33
|
+
*/
|
|
34
|
+
function resolveEnvVar(value) {
|
|
35
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
36
|
+
return process.env[envVar] || '';
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve environment variables in an object of strings
|
|
41
|
+
*/
|
|
42
|
+
function resolveEnvVars(env) {
|
|
43
|
+
if (!env)
|
|
44
|
+
return undefined;
|
|
45
|
+
const resolved = {};
|
|
46
|
+
for (const [key, value] of Object.entries(env)) {
|
|
47
|
+
resolved[key] = resolveEnvVar(value);
|
|
48
|
+
}
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Load configuration from ~/.osborn/config.yaml
|
|
53
|
+
* Creates default config if it doesn't exist
|
|
54
|
+
*/
|
|
55
|
+
export function loadConfig() {
|
|
56
|
+
// Ensure config directory exists
|
|
57
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
58
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
59
|
+
console.log(`š Created config directory: ${CONFIG_DIR}`);
|
|
60
|
+
}
|
|
61
|
+
// Create default config if it doesn't exist
|
|
62
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
63
|
+
const defaultYaml = stringify(DEFAULT_CONFIG);
|
|
64
|
+
writeFileSync(CONFIG_FILE, defaultYaml, 'utf-8');
|
|
65
|
+
console.log(`š Created default config: ${CONFIG_FILE}`);
|
|
66
|
+
return DEFAULT_CONFIG;
|
|
67
|
+
}
|
|
68
|
+
// Load and parse config
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(CONFIG_FILE, 'utf-8');
|
|
71
|
+
const config = parse(content);
|
|
72
|
+
console.log(`š Loaded config from: ${CONFIG_FILE}`);
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
console.error(`ā Failed to load config: ${err.message}`);
|
|
77
|
+
return DEFAULT_CONFIG;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get enabled MCP servers in the format expected by Claude Agent SDK
|
|
82
|
+
*/
|
|
83
|
+
export function getMcpServers(config) {
|
|
84
|
+
const servers = {};
|
|
85
|
+
if (!config.mcpServers) {
|
|
86
|
+
return servers;
|
|
87
|
+
}
|
|
88
|
+
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
89
|
+
// Skip disabled servers
|
|
90
|
+
if (serverConfig.enabled === false) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Build the McpServerConfig for Claude Agent SDK
|
|
94
|
+
if (serverConfig.command) {
|
|
95
|
+
servers[name] = {
|
|
96
|
+
command: serverConfig.command,
|
|
97
|
+
args: serverConfig.args,
|
|
98
|
+
env: resolveEnvVars(serverConfig.env),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Note: SSE/HTTP transport may require different handling
|
|
102
|
+
}
|
|
103
|
+
return servers;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get list of enabled MCP server names (for display)
|
|
107
|
+
*/
|
|
108
|
+
export function getEnabledMcpServerNames(config) {
|
|
109
|
+
if (!config.mcpServers)
|
|
110
|
+
return [];
|
|
111
|
+
return Object.entries(config.mcpServers)
|
|
112
|
+
.filter(([_, serverConfig]) => serverConfig.enabled !== false && serverConfig.command)
|
|
113
|
+
.map(([name, _]) => name);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Save config to file
|
|
117
|
+
*/
|
|
118
|
+
export function saveConfig(config) {
|
|
119
|
+
try {
|
|
120
|
+
const yaml = stringify(config);
|
|
121
|
+
writeFileSync(CONFIG_FILE, yaml, 'utf-8');
|
|
122
|
+
console.log(`š¾ Saved config to: ${CONFIG_FILE}`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.error(`ā Failed to save config: ${err.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
package/dist/index.d.ts
CHANGED