@nils.del/supergrok 1.0.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 (59) hide show
  1. package/.env +45 -0
  2. package/.grok/settings.json +13 -0
  3. package/dist/agent/index.d.ts +35 -0
  4. package/dist/agent/index.d.ts.map +1 -0
  5. package/dist/agent/index.js +244 -0
  6. package/dist/agent/index.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +242 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/mcp/client.d.ts +33 -0
  12. package/dist/mcp/client.d.ts.map +1 -0
  13. package/dist/mcp/client.js +134 -0
  14. package/dist/mcp/client.js.map +1 -0
  15. package/dist/mcp/config.d.ts +15 -0
  16. package/dist/mcp/config.d.ts.map +1 -0
  17. package/dist/mcp/config.js +123 -0
  18. package/dist/mcp/config.js.map +1 -0
  19. package/dist/mcp/transports.d.ts +24 -0
  20. package/dist/mcp/transports.d.ts.map +1 -0
  21. package/dist/mcp/transports.js +91 -0
  22. package/dist/mcp/transports.js.map +1 -0
  23. package/dist/tools/bash.d.ts +28 -0
  24. package/dist/tools/bash.d.ts.map +1 -0
  25. package/dist/tools/bash.js +70 -0
  26. package/dist/tools/bash.js.map +1 -0
  27. package/dist/tools/index.d.ts +77 -0
  28. package/dist/tools/index.d.ts.map +1 -0
  29. package/dist/tools/index.js +80 -0
  30. package/dist/tools/index.js.map +1 -0
  31. package/dist/tools/text-editor.d.ts +69 -0
  32. package/dist/tools/text-editor.d.ts.map +1 -0
  33. package/dist/tools/text-editor.js +178 -0
  34. package/dist/tools/text-editor.js.map +1 -0
  35. package/dist/tools/types.d.ts +14 -0
  36. package/dist/tools/types.d.ts.map +1 -0
  37. package/dist/tools/types.js +2 -0
  38. package/dist/tools/types.js.map +1 -0
  39. package/dist/ui/readline-ui.d.ts +32 -0
  40. package/dist/ui/readline-ui.d.ts.map +1 -0
  41. package/dist/ui/readline-ui.js +256 -0
  42. package/dist/ui/readline-ui.js.map +1 -0
  43. package/dist/utils/settings.d.ts +20 -0
  44. package/dist/utils/settings.d.ts.map +1 -0
  45. package/dist/utils/settings.js +101 -0
  46. package/dist/utils/settings.js.map +1 -0
  47. package/package.json +51 -0
  48. package/src/agent/index.ts +302 -0
  49. package/src/index.ts +275 -0
  50. package/src/mcp/client.ts +178 -0
  51. package/src/mcp/config.ts +149 -0
  52. package/src/mcp/transports.ts +142 -0
  53. package/src/tools/bash.ts +84 -0
  54. package/src/tools/index.ts +91 -0
  55. package/src/tools/text-editor.ts +222 -0
  56. package/src/tools/types.ts +14 -0
  57. package/src/ui/readline-ui.ts +294 -0
  58. package/src/utils/settings.ts +129 -0
  59. package/tsconfig.json +20 -0
package/src/index.ts ADDED
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import * as dotenv from 'dotenv';
4
+ import chalk from 'chalk';
5
+ import { GrokAgent } from './agent/index.js';
6
+ import { runReadlineUI } from './ui/readline-ui.js';
7
+ import { getApiKey, getBaseURL, getCurrentModel, saveUserSettings, loadUserSettings } from './utils/settings.js';
8
+
9
+ // Load environment variables
10
+ dotenv.config();
11
+
12
+ // Handle uncaught exceptions
13
+ process.on('uncaughtException', (error) => {
14
+ console.error(chalk.red('Uncaught exception:'), error.message);
15
+ process.exit(1);
16
+ });
17
+
18
+ process.on('unhandledRejection', (reason: any) => {
19
+ console.error(chalk.red('Unhandled rejection:'), reason?.message || reason);
20
+ process.exit(1);
21
+ });
22
+
23
+ // Headless mode processing
24
+ async function processPromptHeadless(
25
+ prompt: string,
26
+ apiKey: string,
27
+ baseURL?: string,
28
+ model?: string,
29
+ maxToolRounds?: number
30
+ ): Promise<void> {
31
+ try {
32
+ const agent = new GrokAgent(apiKey, baseURL, model, maxToolRounds);
33
+
34
+ console.log(chalk.gray('Processing...'));
35
+ console.log();
36
+
37
+ for await (const chunk of agent.processUserMessageStream(prompt)) {
38
+ switch (chunk.type) {
39
+ case 'content':
40
+ if (chunk.content) {
41
+ process.stdout.write(chunk.content);
42
+ }
43
+ break;
44
+
45
+ case 'tool_calls':
46
+ if (chunk.toolCalls) {
47
+ console.log();
48
+ for (const tc of chunk.toolCalls) {
49
+ console.log(chalk.blue(`⚡ ${tc.function.name}`));
50
+ }
51
+ }
52
+ break;
53
+
54
+ case 'tool_result':
55
+ if (chunk.toolResult) {
56
+ if (chunk.toolResult.success) {
57
+ console.log(chalk.green('✓'), chalk.gray(chunk.toolResult.output?.substring(0, 200) || 'Success'));
58
+ } else {
59
+ console.log(chalk.red('✗'), chunk.toolResult.error);
60
+ }
61
+ }
62
+ break;
63
+
64
+ case 'done':
65
+ console.log();
66
+ break;
67
+ }
68
+ }
69
+
70
+ await agent.shutdown();
71
+ } catch (error: any) {
72
+ console.error(chalk.red('Error:'), error.message);
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ program
78
+ .name('grok-fixed')
79
+ .description('Grok CLI (Fixed Edition) - Works properly with MCP on Linux. No Ink flickering issues.')
80
+ .version('1.0.0')
81
+ .argument('[message...]', 'Initial message to send to Grok')
82
+ .option('-d, --directory <dir>', 'Set working directory', process.cwd())
83
+ .option('-k, --api-key <key>', 'Grok API key (or set GROK_API_KEY env var)')
84
+ .option('-u, --base-url <url>', 'Grok API base URL (or set GROK_BASE_URL env var)')
85
+ .option('-m, --model <model>', 'AI model to use (or set GROK_MODEL env var)')
86
+ .option('-p, --prompt <prompt>', 'Process a single prompt and exit (headless mode)')
87
+ .option('--max-tool-rounds <rounds>', 'Maximum tool execution rounds (default: 400)', '400')
88
+ .action(async (message, options) => {
89
+ // Change working directory if specified
90
+ if (options.directory) {
91
+ try {
92
+ process.chdir(options.directory);
93
+ } catch (error: any) {
94
+ console.error(chalk.red(`Error changing directory: ${error.message}`));
95
+ process.exit(1);
96
+ }
97
+ }
98
+
99
+ try {
100
+ // Get API key
101
+ const apiKey = options.apiKey || getApiKey();
102
+ const baseURL = options.baseUrl || getBaseURL();
103
+ const model = options.model || getCurrentModel();
104
+ const maxToolRounds = parseInt(options.maxToolRounds) || 400;
105
+
106
+ if (!apiKey) {
107
+ console.error(chalk.red('Error: API key required.'));
108
+ console.error(chalk.gray('Set GROK_API_KEY environment variable, use --api-key flag,'));
109
+ console.error(chalk.gray('or add "apiKey" to ~/.grok/user-settings.json'));
110
+ process.exit(1);
111
+ }
112
+
113
+ // Save API key if provided via command line
114
+ if (options.apiKey) {
115
+ const settings = loadUserSettings();
116
+ settings.apiKey = options.apiKey;
117
+ if (options.baseUrl) {
118
+ settings.baseURL = options.baseUrl;
119
+ }
120
+ saveUserSettings(settings);
121
+ console.log(chalk.green('✓ API key saved to ~/.grok/user-settings.json'));
122
+ }
123
+
124
+ // Headless mode
125
+ if (options.prompt) {
126
+ await processPromptHeadless(options.prompt, apiKey, baseURL, model, maxToolRounds);
127
+ return;
128
+ }
129
+
130
+ // Interactive mode
131
+ const agent = new GrokAgent(apiKey, baseURL, model, maxToolRounds);
132
+ const initialMessage = Array.isArray(message) ? message.join(' ') : message;
133
+
134
+ await runReadlineUI(agent, initialMessage || undefined);
135
+ } catch (error: any) {
136
+ console.error(chalk.red('Error:'), error.message);
137
+ process.exit(1);
138
+ }
139
+ });
140
+
141
+ // MCP subcommand
142
+ const mcpCommand = program
143
+ .command('mcp')
144
+ .description('Manage MCP (Model Context Protocol) servers');
145
+
146
+ mcpCommand
147
+ .command('list')
148
+ .description('List configured MCP servers')
149
+ .action(async () => {
150
+ const { loadMCPConfig } = await import('./mcp/config.js');
151
+ const config = loadMCPConfig();
152
+
153
+ if (config.servers.length === 0) {
154
+ console.log(chalk.gray('No MCP servers configured.'));
155
+ console.log(chalk.gray('Use "grok-fixed mcp add" to add a server.'));
156
+ return;
157
+ }
158
+
159
+ console.log(chalk.cyan('Configured MCP Servers:'));
160
+ for (const server of config.servers) {
161
+ console.log(chalk.white(` • ${server.name}`));
162
+ console.log(chalk.gray(` Transport: ${server.transport.type}`));
163
+ if (server.transport.type === 'stdio') {
164
+ console.log(chalk.gray(` Command: ${server.transport.command} ${(server.transport.args || []).join(' ')}`));
165
+ } else if ('url' in server.transport) {
166
+ console.log(chalk.gray(` URL: ${server.transport.url}`));
167
+ }
168
+ }
169
+ });
170
+
171
+ mcpCommand
172
+ .command('add <name>')
173
+ .description('Add a new MCP server')
174
+ .option('-t, --transport <type>', 'Transport type (stdio, http, sse)', 'stdio')
175
+ .option('-c, --command <command>', 'Command to run (for stdio transport)')
176
+ .option('-a, --args <args...>', 'Arguments for the command')
177
+ .option('-u, --url <url>', 'URL (for http/sse transport)')
178
+ .option('-e, --env <env...>', 'Environment variables (KEY=value format)')
179
+ .action(async (name, options) => {
180
+ const { saveMCPServerConfig } = await import('./mcp/config.js');
181
+
182
+ let transport: any;
183
+
184
+ if (options.transport === 'stdio') {
185
+ if (!options.command) {
186
+ console.error(chalk.red('Error: --command is required for stdio transport'));
187
+ process.exit(1);
188
+ }
189
+
190
+ const env: Record<string, string> = {};
191
+ if (options.env) {
192
+ for (const e of options.env) {
193
+ const [key, ...valueParts] = e.split('=');
194
+ env[key] = valueParts.join('=');
195
+ }
196
+ }
197
+
198
+ transport = {
199
+ type: 'stdio',
200
+ command: options.command,
201
+ args: options.args || [],
202
+ env
203
+ };
204
+ } else if (options.transport === 'http' || options.transport === 'sse') {
205
+ if (!options.url) {
206
+ console.error(chalk.red(`Error: --url is required for ${options.transport} transport`));
207
+ process.exit(1);
208
+ }
209
+
210
+ transport = {
211
+ type: options.transport,
212
+ url: options.url
213
+ };
214
+ } else {
215
+ console.error(chalk.red(`Error: Unknown transport type: ${options.transport}`));
216
+ process.exit(1);
217
+ }
218
+
219
+ try {
220
+ saveMCPServerConfig({ name, transport });
221
+ console.log(chalk.green(`✓ MCP server "${name}" added successfully`));
222
+ } catch (error: any) {
223
+ console.error(chalk.red(`Error: ${error.message}`));
224
+ process.exit(1);
225
+ }
226
+ });
227
+
228
+ mcpCommand
229
+ .command('remove <name>')
230
+ .description('Remove an MCP server')
231
+ .action(async (name) => {
232
+ const { removeMCPServerConfig } = await import('./mcp/config.js');
233
+
234
+ if (removeMCPServerConfig(name)) {
235
+ console.log(chalk.green(`✓ MCP server "${name}" removed`));
236
+ } else {
237
+ console.log(chalk.yellow(`Server "${name}" not found`));
238
+ }
239
+ });
240
+
241
+ mcpCommand
242
+ .command('test [name]')
243
+ .description('Test connection to MCP server(s)')
244
+ .action(async (name) => {
245
+ const { loadMCPConfig } = await import('./mcp/config.js');
246
+ const { getMCPManager } = await import('./mcp/client.js');
247
+
248
+ const config = loadMCPConfig();
249
+ const serversToTest = name
250
+ ? config.servers.filter(s => s.name === name)
251
+ : config.servers;
252
+
253
+ if (serversToTest.length === 0) {
254
+ console.log(chalk.yellow('No servers to test.'));
255
+ return;
256
+ }
257
+
258
+ const mcpManager = getMCPManager();
259
+
260
+ for (const server of serversToTest) {
261
+ process.stdout.write(chalk.gray(`Testing ${server.name}... `));
262
+
263
+ try {
264
+ await mcpManager.addServer(server);
265
+ const tools = mcpManager.getTools().filter(t => t.serverName === server.name);
266
+ console.log(chalk.green(`✓ Connected (${tools.length} tools)`));
267
+ } catch (error: any) {
268
+ console.log(chalk.red(`✗ Failed: ${error.message}`));
269
+ }
270
+ }
271
+
272
+ await mcpManager.shutdown();
273
+ });
274
+
275
+ program.parse();
@@ -0,0 +1,178 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3
+ import { EventEmitter } from "events";
4
+ import { createTransport, MCPTransport, TransportType, TransportConfig } from "./transports.js";
5
+
6
+ export interface MCPServerConfig {
7
+ name: string;
8
+ transport: TransportConfig;
9
+ command?: string;
10
+ args?: string[];
11
+ env?: Record<string, string>;
12
+ }
13
+
14
+ export interface MCPTool {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: any;
18
+ serverName: string;
19
+ }
20
+
21
+ export class MCPManager extends EventEmitter {
22
+ private clients: Map<string, Client> = new Map();
23
+ private transports: Map<string, MCPTransport> = new Map();
24
+ private tools: Map<string, MCPTool> = new Map();
25
+ private initPromise: Promise<void> | null = null;
26
+
27
+ async addServer(config: MCPServerConfig): Promise<void> {
28
+ try {
29
+ let transportConfig = config.transport;
30
+ if (!transportConfig && config.command) {
31
+ transportConfig = {
32
+ type: 'stdio',
33
+ command: config.command,
34
+ args: config.args,
35
+ env: config.env
36
+ };
37
+ }
38
+
39
+ if (!transportConfig) {
40
+ throw new Error('Transport configuration is required');
41
+ }
42
+
43
+ const transport = createTransport(transportConfig);
44
+ this.transports.set(config.name, transport);
45
+
46
+ const client = new Client({
47
+ name: "grok-cli-fixed",
48
+ version: "1.0.0"
49
+ });
50
+
51
+ this.clients.set(config.name, client);
52
+
53
+ const sdkTransport = await transport.connect();
54
+ await client.connect(sdkTransport);
55
+
56
+ const toolsResult = await client.listTools();
57
+
58
+ for (const tool of toolsResult.tools) {
59
+ const mcpTool: MCPTool = {
60
+ name: `mcp__${config.name}__${tool.name}`,
61
+ description: tool.description || `Tool from ${config.name} server`,
62
+ inputSchema: tool.inputSchema,
63
+ serverName: config.name
64
+ };
65
+ this.tools.set(mcpTool.name, mcpTool);
66
+ }
67
+
68
+ this.emit('serverAdded', config.name, toolsResult.tools.length);
69
+ } catch (error) {
70
+ this.emit('serverError', config.name, error);
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ async removeServer(serverName: string): Promise<void> {
76
+ for (const [toolName, tool] of this.tools.entries()) {
77
+ if (tool.serverName === serverName) {
78
+ this.tools.delete(toolName);
79
+ }
80
+ }
81
+
82
+ const client = this.clients.get(serverName);
83
+ if (client) {
84
+ await client.close();
85
+ this.clients.delete(serverName);
86
+ }
87
+
88
+ const transport = this.transports.get(serverName);
89
+ if (transport) {
90
+ await transport.disconnect();
91
+ this.transports.delete(serverName);
92
+ }
93
+
94
+ this.emit('serverRemoved', serverName);
95
+ }
96
+
97
+ async callTool(toolName: string, arguments_: any): Promise<CallToolResult> {
98
+ const tool = this.tools.get(toolName);
99
+ if (!tool) {
100
+ throw new Error(`Tool ${toolName} not found`);
101
+ }
102
+
103
+ const client = this.clients.get(tool.serverName);
104
+ if (!client) {
105
+ throw new Error(`Server ${tool.serverName} not connected`);
106
+ }
107
+
108
+ const originalToolName = toolName.replace(`mcp__${tool.serverName}__`, '');
109
+
110
+ const result = await client.callTool({
111
+ name: originalToolName,
112
+ arguments: arguments_
113
+ });
114
+
115
+ // Ensure result has proper content structure
116
+ return {
117
+ content: result.content || [],
118
+ isError: result.isError,
119
+ _meta: result._meta
120
+ } as CallToolResult;
121
+ }
122
+
123
+ getTools(): MCPTool[] {
124
+ return Array.from(this.tools.values());
125
+ }
126
+
127
+ getServers(): string[] {
128
+ return Array.from(this.clients.keys());
129
+ }
130
+
131
+ async shutdown(): Promise<void> {
132
+ const serverNames = Array.from(this.clients.keys());
133
+ await Promise.all(serverNames.map(name => this.removeServer(name)));
134
+ }
135
+
136
+ getTransportType(serverName: string): TransportType | undefined {
137
+ const transport = this.transports.get(serverName);
138
+ return transport?.getType();
139
+ }
140
+
141
+ async ensureServersInitialized(): Promise<void> {
142
+ if (this.initPromise) {
143
+ return this.initPromise;
144
+ }
145
+
146
+ if (this.clients.size > 0) {
147
+ return;
148
+ }
149
+
150
+ this.initPromise = this.initializeServers();
151
+ return this.initPromise;
152
+ }
153
+
154
+ private async initializeServers(): Promise<void> {
155
+ const { loadMCPConfig } = await import('./config.js');
156
+ const config = loadMCPConfig();
157
+
158
+ const initPromises = config.servers.map(async (serverConfig) => {
159
+ try {
160
+ await this.addServer(serverConfig);
161
+ } catch (error) {
162
+ // Silently fail for individual servers
163
+ }
164
+ });
165
+
166
+ await Promise.all(initPromises);
167
+ }
168
+ }
169
+
170
+ // Singleton instance
171
+ let mcpManager: MCPManager | null = null;
172
+
173
+ export function getMCPManager(): MCPManager {
174
+ if (!mcpManager) {
175
+ mcpManager = new MCPManager();
176
+ }
177
+ return mcpManager;
178
+ }
@@ -0,0 +1,149 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import type { TransportConfig } from './transports.js';
5
+
6
+ export interface MCPServerConfig {
7
+ name: string;
8
+ transport: TransportConfig;
9
+ command?: string;
10
+ args?: string[];
11
+ env?: Record<string, string>;
12
+ }
13
+
14
+ export interface MCPConfig {
15
+ servers: MCPServerConfig[];
16
+ }
17
+
18
+ function getProjectSettingsPath(): string {
19
+ return path.join(process.cwd(), '.grok', 'settings.json');
20
+ }
21
+
22
+ function getUserSettingsPath(): string {
23
+ return path.join(os.homedir(), '.grok', 'user-settings.json');
24
+ }
25
+
26
+ export function loadMCPConfig(): MCPConfig {
27
+ const config: MCPConfig = { servers: [] };
28
+
29
+ // Try loading project-level settings first
30
+ const projectSettingsPath = getProjectSettingsPath();
31
+ if (fs.existsSync(projectSettingsPath)) {
32
+ try {
33
+ const projectSettings = JSON.parse(fs.readFileSync(projectSettingsPath, 'utf-8'));
34
+ if (projectSettings.mcpServers) {
35
+ for (const [name, serverConfig] of Object.entries(projectSettings.mcpServers)) {
36
+ const server = serverConfig as any;
37
+ config.servers.push({
38
+ name,
39
+ transport: server.transport || {
40
+ type: 'stdio',
41
+ command: server.command,
42
+ args: server.args,
43
+ env: server.env
44
+ },
45
+ command: server.command,
46
+ args: server.args,
47
+ env: server.env
48
+ });
49
+ }
50
+ }
51
+ } catch (error) {
52
+ console.warn('Failed to parse project MCP settings:', error);
53
+ }
54
+ }
55
+
56
+ // Also check user-level settings for global MCP servers
57
+ const userSettingsPath = getUserSettingsPath();
58
+ if (fs.existsSync(userSettingsPath)) {
59
+ try {
60
+ const userSettings = JSON.parse(fs.readFileSync(userSettingsPath, 'utf-8'));
61
+ if (userSettings.mcpServers) {
62
+ for (const [name, serverConfig] of Object.entries(userSettings.mcpServers)) {
63
+ // Don't duplicate if already defined in project settings
64
+ if (!config.servers.find(s => s.name === name)) {
65
+ const server = serverConfig as any;
66
+ config.servers.push({
67
+ name,
68
+ transport: server.transport || {
69
+ type: 'stdio',
70
+ command: server.command,
71
+ args: server.args,
72
+ env: server.env
73
+ },
74
+ command: server.command,
75
+ args: server.args,
76
+ env: server.env
77
+ });
78
+ }
79
+ }
80
+ }
81
+ } catch (error) {
82
+ console.warn('Failed to parse user MCP settings:', error);
83
+ }
84
+ }
85
+
86
+ return config;
87
+ }
88
+
89
+ export function saveMCPServerConfig(serverConfig: MCPServerConfig): void {
90
+ const settingsPath = getProjectSettingsPath();
91
+ const settingsDir = path.dirname(settingsPath);
92
+
93
+ if (!fs.existsSync(settingsDir)) {
94
+ fs.mkdirSync(settingsDir, { recursive: true });
95
+ }
96
+
97
+ let settings: any = {};
98
+ if (fs.existsSync(settingsPath)) {
99
+ try {
100
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
101
+ } catch (error) {
102
+ // Start fresh if parsing fails
103
+ }
104
+ }
105
+
106
+ if (!settings.mcpServers) {
107
+ settings.mcpServers = {};
108
+ }
109
+
110
+ // Save the server configuration
111
+ const serverData: any = {
112
+ name: serverConfig.name,
113
+ transport: serverConfig.transport.type
114
+ };
115
+
116
+ if (serverConfig.transport.type === 'stdio') {
117
+ serverData.command = serverConfig.transport.command;
118
+ serverData.args = serverConfig.transport.args;
119
+ serverData.env = serverConfig.transport.env;
120
+ } else if (serverConfig.transport.type === 'http' || serverConfig.transport.type === 'sse') {
121
+ serverData.url = (serverConfig.transport as any).url;
122
+ }
123
+
124
+ settings.mcpServers[serverConfig.name] = serverData;
125
+
126
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
127
+ }
128
+
129
+ export function removeMCPServerConfig(serverName: string): boolean {
130
+ const settingsPath = getProjectSettingsPath();
131
+
132
+ if (!fs.existsSync(settingsPath)) {
133
+ return false;
134
+ }
135
+
136
+ try {
137
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
138
+
139
+ if (!settings.mcpServers || !settings.mcpServers[serverName]) {
140
+ return false;
141
+ }
142
+
143
+ delete settings.mcpServers[serverName];
144
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
145
+ return true;
146
+ } catch (error) {
147
+ return false;
148
+ }
149
+ }