openclaw-cascade-plugin 1.0.12 → 1.0.14

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 (80) hide show
  1. package/dist/grpc-client.d.ts +17 -0
  2. package/dist/grpc-client.d.ts.map +1 -0
  3. package/dist/grpc-client.js +154 -0
  4. package/dist/grpc-client.js.map +1 -0
  5. package/dist/index.d.ts +2 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +14 -54
  8. package/dist/index.js.map +1 -1
  9. package/dist/test-utils/mocks.d.ts +6 -4
  10. package/dist/test-utils/mocks.d.ts.map +1 -1
  11. package/dist/test-utils/mocks.js +24 -14
  12. package/dist/test-utils/mocks.js.map +1 -1
  13. package/dist/tools/desktop-automation.d.ts +2 -7
  14. package/dist/tools/desktop-automation.d.ts.map +1 -1
  15. package/dist/tools/desktop-automation.js +64 -123
  16. package/dist/tools/desktop-automation.js.map +1 -1
  17. package/dist/tools/index.d.ts +3 -10
  18. package/dist/tools/index.d.ts.map +1 -1
  19. package/dist/tools/index.js +3 -17
  20. package/dist/tools/index.js.map +1 -1
  21. package/openclaw.plugin.json +1 -1
  22. package/package.json +13 -2
  23. package/proto/cascade.proto +297 -0
  24. package/PHASE1_SUMMARY.md +0 -191
  25. package/PHASE3_SUMMARY.md +0 -195
  26. package/dist/cascade-client.d.ts +0 -53
  27. package/dist/cascade-client.d.ts.map +0 -1
  28. package/dist/cascade-client.js +0 -179
  29. package/dist/cascade-client.js.map +0 -1
  30. package/dist/python-manager.d.ts +0 -59
  31. package/dist/python-manager.d.ts.map +0 -1
  32. package/dist/python-manager.js +0 -190
  33. package/dist/python-manager.js.map +0 -1
  34. package/dist/tools/api-tools.d.ts +0 -9
  35. package/dist/tools/api-tools.d.ts.map +0 -1
  36. package/dist/tools/api-tools.js +0 -102
  37. package/dist/tools/api-tools.js.map +0 -1
  38. package/dist/tools/sandbox-tools.d.ts +0 -9
  39. package/dist/tools/sandbox-tools.d.ts.map +0 -1
  40. package/dist/tools/sandbox-tools.js +0 -79
  41. package/dist/tools/sandbox-tools.js.map +0 -1
  42. package/dist/tools/web-automation.d.ts +0 -9
  43. package/dist/tools/web-automation.d.ts.map +0 -1
  44. package/dist/tools/web-automation.js +0 -471
  45. package/dist/tools/web-automation.js.map +0 -1
  46. package/jest.setup.js +0 -19
  47. package/openclaw-cascade-plugin-1.0.0.tgz +0 -0
  48. package/openclaw-cascade-plugin-1.0.10.tgz +0 -0
  49. package/openclaw-cascade-plugin-1.0.11.tgz +0 -0
  50. package/openclaw-cascade-plugin-1.0.12.tgz +0 -0
  51. package/openclaw-cascade-plugin-1.0.4.tgz +0 -0
  52. package/openclaw-cascade-plugin-1.0.6.tgz +0 -0
  53. package/openclaw-cascade-plugin-1.0.7.tgz +0 -0
  54. package/openclaw-cascade-plugin-1.0.8.tgz +0 -0
  55. package/openclaw-cascade-plugin-1.0.9.tgz +0 -0
  56. package/scripts/postinstall.js +0 -84
  57. package/src/a2a-client.ts +0 -66
  58. package/src/cascade-client.test.ts +0 -400
  59. package/src/cascade-client.ts +0 -198
  60. package/src/config.test.ts +0 -189
  61. package/src/config.ts +0 -137
  62. package/src/index.ts +0 -202
  63. package/src/python-manager.test.ts +0 -187
  64. package/src/python-manager.ts +0 -230
  65. package/src/test-utils/helpers.ts +0 -107
  66. package/src/test-utils/index.ts +0 -2
  67. package/src/test-utils/mocks.ts +0 -101
  68. package/src/tools/a2a-tools.ts +0 -162
  69. package/src/tools/api-tools.ts +0 -110
  70. package/src/tools/desktop-automation.test.ts +0 -308
  71. package/src/tools/desktop-automation.ts +0 -366
  72. package/src/tools/index.ts +0 -13
  73. package/src/tools/response-helpers.ts +0 -78
  74. package/src/tools/sandbox-tools.ts +0 -83
  75. package/src/tools/tool-registry.ts +0 -51
  76. package/src/tools/web-automation.test.ts +0 -177
  77. package/src/tools/web-automation.ts +0 -518
  78. package/src/types/index.ts +0 -133
  79. package/src/wsl.ts +0 -53
  80. package/tsconfig.json +0 -27
package/src/index.ts DELETED
@@ -1,202 +0,0 @@
1
- /**
2
- * OpenClaw Plugin Entry Point
3
- *
4
- * This is the main entry point for the @cascade/openclaw-plugin
5
- * It initializes the plugin and registers all tools with OpenClaw
6
- */
7
-
8
- import { PythonManager } from './python-manager';
9
- import { CascadeMcpClient } from './cascade-client';
10
- import { CascadeA2AClient } from './a2a-client';
11
- import { loadConfig } from './config';
12
- import { CascadeError } from './types';
13
- import { delimiter as pathDelimiter } from 'path';
14
- import {
15
- registerDesktopTools,
16
- registerWebTools,
17
- registerApiTools,
18
- registerSandboxTools,
19
- registerA2ATools
20
- } from './tools';
21
- import { ToolRegistry } from './tools/tool-registry';
22
-
23
- // Placeholder for OpenClaw API type
24
- interface OpenClawApi {
25
- config: {
26
- plugins: {
27
- entries: {
28
- cascade?: { config?: any };
29
- 'openclaw-cascade-plugin'?: { config?: any };
30
- [key: string]: { config?: any } | undefined;
31
- };
32
- };
33
- };
34
- registerTool: (tool: any) => void;
35
- registerGatewayMethod: (name: string, handler: Function) => void;
36
- registerCli: (handler: Function) => void;
37
- notify: (message: string) => void;
38
- }
39
-
40
- export default async function register(api: OpenClawApi) {
41
- let mcpClient: CascadeMcpClient | null = null;
42
- let a2aClient: CascadeA2AClient | null = null;
43
-
44
- try {
45
- // Load and validate configuration
46
- const entries = api.config.plugins.entries || {};
47
- const config = await loadConfig(
48
- entries['openclaw-cascade-plugin']?.config ||
49
- entries.cascade?.config ||
50
- {}
51
- );
52
-
53
- // Lazy loaded clients
54
- let pythonPath: string | null = null;
55
- let initialized = false;
56
-
57
- const getMcpClient = async (): Promise<CascadeMcpClient> => {
58
- if (mcpClient && initialized) return mcpClient;
59
-
60
- // Initialize Python Manager
61
- const pythonManager = new PythonManager(config);
62
- pythonPath = await pythonManager.findOrInstallPython();
63
-
64
- // Initialize MCP Client
65
- const pythonEnv: NodeJS.ProcessEnv = {
66
- CASCADE_GRPC_ENDPOINT: config.cascadeGrpcEndpoint,
67
- CASCADE_APP_ID: config.firestoreProjectId || 'openclaw',
68
- CASCADE_USER_ID: 'openclaw-user',
69
- ...(config.firestoreCredentialsPath && {
70
- GOOGLE_APPLICATION_CREDENTIALS: config.firestoreCredentialsPath
71
- })
72
- };
73
-
74
- let modulePath = config.cascadePythonModulePath || process.env.CASCADE_PYTHON_MODULE_PATH;
75
-
76
- // Auto-detect Cascade python module path relative to plugin installation
77
- if (!modulePath) {
78
- const { join } = require('path');
79
- const { existsSync } = require('fs');
80
- // __dirname is openclaw-plugin/dist
81
- // repo root python is openclaw-plugin/../python
82
- const guessedPath = join(__dirname, '..', '..', 'python');
83
- if (existsSync(join(guessedPath, 'mcp_server', '__init__.py'))) {
84
- modulePath = guessedPath;
85
- }
86
- }
87
-
88
- if (modulePath) {
89
- const existingPath = process.env.PYTHONPATH || '';
90
- pythonEnv.PYTHONPATH = existingPath
91
- ? `${modulePath}${pathDelimiter}${existingPath}`
92
- : modulePath;
93
- }
94
-
95
- mcpClient = new CascadeMcpClient(pythonPath, pythonEnv);
96
- await mcpClient.start();
97
- initialized = true;
98
- return mcpClient;
99
- };
100
-
101
- const getA2aClient = async (): Promise<CascadeA2AClient | null> => {
102
- if (!config.enableA2A) return null;
103
- if (a2aClient) return a2aClient;
104
-
105
- a2aClient = new CascadeA2AClient(
106
- config.cascadeGrpcEndpoint,
107
- 'openclaw-user',
108
- config.firestoreProjectId || 'openclaw',
109
- '' // auth token would come from config
110
- );
111
- await a2aClient.initialize();
112
- return a2aClient;
113
- };
114
-
115
- // Create tool registry
116
- const toolRegistry = new ToolRegistry();
117
-
118
- // Register all tools using the getters
119
- registerDesktopTools(toolRegistry, getMcpClient, config);
120
- registerWebTools(toolRegistry, getMcpClient);
121
- registerApiTools(toolRegistry, getMcpClient);
122
- registerSandboxTools(toolRegistry, getMcpClient);
123
- registerA2ATools(toolRegistry, getA2aClient);
124
-
125
- // Register tools with OpenClaw
126
- const tools = toolRegistry.getAll();
127
-
128
- for (const tool of tools) {
129
- // Ensure the schema is perfectly formatted for OpenAI and OpenClaw's internal validator
130
- const schema = tool.inputSchema || { type: 'object', properties: {} };
131
-
132
- api.registerTool({
133
- name: tool.name,
134
- description: tool.description,
135
- schema: schema,
136
- parameters: schema,
137
- inputSchema: schema,
138
- // Match the exact signature expected by OpenClaw's pi-agent-core AgentTool
139
- execute: async (_toolCallId: string, params: any) => tool.handler(params),
140
- handler: tool.handler // Fallback for legacy
141
- });
142
- }
143
-
144
- // Register status check
145
- api.registerGatewayMethod('cascade.status', () => ({
146
- connected: mcpClient?.isConnected() || false,
147
- toolsRegistered: tools.length,
148
- pythonPath,
149
- grpcEndpoint: config.cascadeGrpcEndpoint,
150
- a2aEnabled: config.enableA2A
151
- }));
152
-
153
- // Register CLI command
154
- api.registerCli(({ program }: { program: any }) => {
155
- program
156
- .command('cascade:status')
157
- .description('Check Cascade plugin status')
158
- .action(async () => {
159
- let connected = false;
160
- try {
161
- // Only check if it's already initialized to avoid triggering init
162
- if (initialized && mcpClient) {
163
- connected = mcpClient.isConnected();
164
- }
165
- } catch(e) {}
166
-
167
- console.log('Cascade Plugin Status:');
168
- console.log(' Connected:', connected);
169
- console.log(' Tools:', tools.length);
170
- console.log(' Python:', pythonPath || 'Not yet initialized');
171
- console.log(' gRPC:', config.cascadeGrpcEndpoint);
172
- console.log(' A2A:', config.enableA2A ? 'enabled' : 'disabled');
173
- });
174
-
175
- program
176
- .command('cascade:tools')
177
- .description('List all registered tools')
178
- .action(() => {
179
- console.log('Registered Tools:');
180
- tools.forEach((tool, index) => {
181
- console.log(` ${index + 1}. ${tool.name}`);
182
- });
183
- });
184
- });
185
-
186
- } catch (error) {
187
- console.error('Failed to initialize Cascade plugin:', error);
188
-
189
- if (error instanceof CascadeError) {
190
- throw error;
191
- }
192
-
193
- throw new Error(
194
- `Cascade plugin initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`
195
- );
196
- }
197
- }
198
-
199
- // Export types for TypeScript users
200
- export * from './types';
201
- export { PythonManager, CascadeMcpClient, CascadeA2AClient, loadConfig };
202
- // Note: ToolRegistry is internal use only - not exported to avoid conflicts
@@ -1,187 +0,0 @@
1
- /**
2
- * Tests for PythonManager
3
- *
4
- * Test-driven development: Write tests first, then implement
5
- */
6
-
7
- import { PythonManager } from './python-manager';
8
- import { createMockConfig } from './test-utils';
9
-
10
- describe('PythonManager', () => {
11
- let pythonManager: PythonManager;
12
- let mockExec: jest.Mock;
13
-
14
- beforeEach(() => {
15
- jest.clearAllMocks();
16
- mockExec = jest.fn();
17
-
18
- // Mock the exec method directly on the module
19
- jest.doMock('child_process', () => ({
20
- exec: mockExec
21
- }));
22
-
23
- pythonManager = new PythonManager(createMockConfig());
24
- });
25
-
26
- afterEach(() => {
27
- jest.dontMock('child_process');
28
- });
29
-
30
- describe('findOrInstallPython', () => {
31
- test('should return configured path when cascadePythonPath is set', async () => {
32
- // Arrange
33
- const config = createMockConfig({ cascadePythonPath: '/custom/python' });
34
- pythonManager = new PythonManager(config);
35
-
36
- // Mock the execAsync method directly
37
- const execSpy = jest.spyOn(pythonManager as any, 'execAsync')
38
- .mockResolvedValue({ stdout: 'Python 3.12.0', stderr: '' });
39
-
40
- // Act
41
- const result = await pythonManager.findOrInstallPython();
42
-
43
- // Assert
44
- expect(result).toBe('/custom/python');
45
- expect(execSpy).toHaveBeenCalledWith('/custom/python --version');
46
- });
47
-
48
- test('should auto-detect python3.12 in common locations', async () => {
49
- // Arrange
50
- let callCount = 0;
51
- const execSpy = jest.spyOn(pythonManager as any, 'execAsync')
52
- .mockImplementation(() => {
53
- callCount++;
54
- if (callCount <= 2) {
55
- return Promise.reject(new Error('not found'));
56
- }
57
- return Promise.resolve({ stdout: 'Python 3.10.0', stderr: '' });
58
- });
59
-
60
- // Act
61
- const result = await pythonManager.findOrInstallPython();
62
-
63
- // Assert
64
- expect(result).toBe('python3.10');
65
- expect(execSpy).toHaveBeenCalledWith('python3.12 --version');
66
- expect(execSpy).toHaveBeenCalledWith('python3.11 --version');
67
- expect(execSpy).toHaveBeenCalledWith('python3.10 --version');
68
- });
69
-
70
- test('should fallback through version candidates in order', async () => {
71
- // Arrange
72
- jest.spyOn(pythonManager as any, 'execAsync')
73
- .mockResolvedValue({ stdout: 'Python 3.12.1', stderr: '' });
74
-
75
- // Act
76
- const result = await pythonManager.findOrInstallPython();
77
-
78
- // Assert
79
- expect(result).toBe('python3.12');
80
- });
81
-
82
- test('should throw error when no Python found and auto-install fails', async () => {
83
- // Arrange
84
- jest.spyOn(pythonManager as any, 'execAsync').mockRejectedValue(new Error('not found'));
85
-
86
- // Mock install to fail
87
- jest.spyOn(pythonManager as any, 'installPython').mockRejectedValue(
88
- new Error('Installation failed')
89
- );
90
-
91
- // Act & Assert
92
- await expect(pythonManager.findOrInstallPython()).rejects.toThrow('Installation failed');
93
- });
94
- });
95
-
96
- describe('isValidPython', () => {
97
- test('should return true for Python 3.10+', async () => {
98
- // Arrange
99
- jest.spyOn(pythonManager as any, 'execAsync')
100
- .mockResolvedValue({ stdout: 'Python 3.12.0', stderr: '' });
101
-
102
- // Act
103
- const result = await (pythonManager as any).isValidPython('python3');
104
-
105
- // Assert
106
- expect(result).toBe(true);
107
- });
108
-
109
- test('should return true for Python 3.10.x', async () => {
110
- // Arrange
111
- jest.spyOn(pythonManager as any, 'execAsync')
112
- .mockResolvedValue({ stdout: 'Python 3.10.5', stderr: '' });
113
-
114
- // Act
115
- const result = await (pythonManager as any).isValidPython('python3');
116
-
117
- // Assert
118
- expect(result).toBe(true);
119
- });
120
-
121
- test('should return false for Python 3.9', async () => {
122
- // Arrange
123
- jest.spyOn(pythonManager as any, 'execAsync')
124
- .mockResolvedValue({ stdout: 'Python 3.9.7', stderr: '' });
125
-
126
- // Act
127
- const result = await (pythonManager as any).isValidPython('python3');
128
-
129
- // Assert
130
- expect(result).toBe(false);
131
- });
132
-
133
- test('should return false for Python 2.x', async () => {
134
- // Arrange
135
- jest.spyOn(pythonManager as any, 'execAsync')
136
- .mockResolvedValue({ stdout: 'Python 2.7.18', stderr: '' });
137
-
138
- // Act
139
- const result = await (pythonManager as any).isValidPython('python');
140
-
141
- // Assert
142
- expect(result).toBe(false);
143
- });
144
-
145
- test('should return false for non-Python executables', async () => {
146
- // Arrange
147
- jest.spyOn(pythonManager as any, 'execAsync')
148
- .mockResolvedValue({ stdout: 'not python output', stderr: '' });
149
-
150
- // Act
151
- const result = await (pythonManager as any).isValidPython('notpython');
152
-
153
- // Assert
154
- expect(result).toBe(false);
155
- });
156
-
157
- test('should return false when command not found', async () => {
158
- // Arrange
159
- jest.spyOn(pythonManager as any, 'execAsync')
160
- .mockRejectedValue(new Error('command not found'));
161
-
162
- // Act
163
- const result = await (pythonManager as any).isValidPython('fakepython');
164
-
165
- // Assert
166
- expect(result).toBe(false);
167
- });
168
- });
169
-
170
- describe('parsePythonVersion', () => {
171
- test('should parse standard version output', () => {
172
- // Act & Assert
173
- expect((pythonManager as any).parsePythonVersion('Python 3.12.0')).toBe(3.12);
174
- expect((pythonManager as any).parsePythonVersion('Python 3.10.5')).toBe(3.10);
175
- expect((pythonManager as any).parsePythonVersion('Python 3.9.0')).toBe(3.09);
176
- });
177
-
178
- test('should handle version without patch', () => {
179
- expect((pythonManager as any).parsePythonVersion('Python 3.12')).toBe(3.12);
180
- });
181
-
182
- test('should return 0 for invalid output', () => {
183
- expect((pythonManager as any).parsePythonVersion('not python')).toBe(0);
184
- expect((pythonManager as any).parsePythonVersion('')).toBe(0);
185
- });
186
- });
187
- });
@@ -1,230 +0,0 @@
1
- /**
2
- * Python Manager - Handles Python environment detection and installation
3
- */
4
-
5
- import { exec } from 'child_process';
6
- import { join } from 'path';
7
- import { CascadePluginConfig } from './types';
8
-
9
- export class PythonManager {
10
- private readonly MIN_PYTHON_VERSION = 3.10;
11
-
12
- constructor(private config: CascadePluginConfig) {}
13
-
14
- /**
15
- * Find or install Python for Cascade
16
- * Priority: 1) Configured path 2) Auto-detect 3) Auto-install
17
- */
18
- async findOrInstallPython(): Promise<string> {
19
- // 1. Check configured path
20
- if (this.config.cascadePythonPath) {
21
- if (await this.isValidPython(this.config.cascadePythonPath)) {
22
- return this.config.cascadePythonPath;
23
- }
24
- console.warn(`Configured Python path ${this.config.cascadePythonPath} is not valid, searching...`);
25
- }
26
-
27
- // 2. Auto-detect in common locations
28
- const detected = await this.autoDetectPython();
29
- if (detected) {
30
- return detected;
31
- }
32
-
33
- // 3. Auto-install
34
- console.log('Python not found. Installing...');
35
- return this.installPython();
36
- }
37
-
38
- /**
39
- * Auto-detect Python in common locations
40
- */
41
- private async autoDetectPython(): Promise<string | null> {
42
- const candidates = this.getPythonCandidates();
43
-
44
- for (const cmd of candidates) {
45
- if (await this.isValidPython(cmd)) {
46
- return cmd;
47
- }
48
- }
49
-
50
- return null;
51
- }
52
-
53
- /**
54
- * Get list of Python commands to try
55
- */
56
- private getPythonCandidates(): string[] {
57
- const candidates = [
58
- 'python3.12',
59
- 'python3.11',
60
- 'python3.10',
61
- 'python3',
62
- ];
63
-
64
- if (process.platform === 'win32') {
65
- // Windows-specific paths
66
- const localAppData = process.env.LOCALAPPDATA || '';
67
- const programFiles = process.env.ProgramFiles || '';
68
-
69
- candidates.push(
70
- join(localAppData, 'Programs', 'Python', 'Python312', 'python.exe'),
71
- join(localAppData, 'Programs', 'Python', 'Python311', 'python.exe'),
72
- join(localAppData, 'Programs', 'Python', 'Python310', 'python.exe'),
73
- join(programFiles, 'Python312', 'python.exe'),
74
- join(programFiles, 'Python311', 'python.exe'),
75
- join(programFiles, 'Python310', 'python.exe'),
76
- 'python.exe',
77
- 'python'
78
- );
79
- } else {
80
- // Unix-like paths
81
- candidates.push(
82
- '/usr/bin/python3.12',
83
- '/usr/bin/python3.11',
84
- '/usr/bin/python3.10',
85
- '/usr/bin/python3',
86
- '/usr/local/bin/python3.12',
87
- '/usr/local/bin/python3.11',
88
- '/usr/local/bin/python3.10',
89
- '/usr/local/bin/python3',
90
- '/opt/homebrew/bin/python3.12',
91
- '/opt/homebrew/bin/python3.11',
92
- '/opt/homebrew/bin/python3.10',
93
- '/opt/homebrew/bin/python3'
94
- );
95
- }
96
-
97
- return candidates;
98
- }
99
-
100
- /**
101
- * Check if Python command is valid and meets version requirements
102
- */
103
- async isValidPython(cmd: string): Promise<boolean> {
104
- try {
105
- const { stdout } = await this.execAsync(`${cmd} --version`);
106
- const version = this.parsePythonVersion(stdout);
107
- return version >= this.MIN_PYTHON_VERSION;
108
- } catch {
109
- return false;
110
- }
111
- }
112
-
113
- /**
114
- * Parse Python version from version string
115
- */
116
- parsePythonVersion(versionOutput: string): number {
117
- const match = versionOutput.match(/Python (\d+)\.(\d+)/);
118
- if (!match) return 0;
119
-
120
- const major = parseInt(match[1], 10);
121
- const minor = parseInt(match[2], 10);
122
- return major + minor / 100;
123
- }
124
-
125
- /**
126
- * Execute command and return stdout
127
- */
128
- private execAsync(command: string): Promise<{ stdout: string; stderr: string }> {
129
- return new Promise((resolve, reject) => {
130
- exec(command, (error, stdout, stderr) => {
131
- if (error) {
132
- reject(error);
133
- } else {
134
- resolve({ stdout, stderr });
135
- }
136
- });
137
- });
138
- }
139
-
140
- /**
141
- * Auto-install Python based on platform
142
- */
143
- private async installPython(): Promise<string> {
144
- if (process.platform === 'win32') {
145
- return this.installPythonWindows();
146
- } else if (process.platform === 'darwin') {
147
- return this.installPythonMacOS();
148
- } else {
149
- return this.installPythonLinux();
150
- }
151
- }
152
-
153
- /**
154
- * Install Python on Windows
155
- */
156
- private async installPythonWindows(): Promise<string> {
157
- try {
158
- const installerPath = await this.downloadPythonInstaller();
159
- await this.runPythonInstaller(installerPath);
160
- return 'python';
161
- } catch (error) {
162
- throw new Error(
163
- `Failed to install Python on Windows: ${error instanceof Error ? error.message : 'Unknown error'}. ` +
164
- 'Please install Python 3.10+ manually from https://python.org'
165
- );
166
- }
167
- }
168
-
169
- /**
170
- * Download Python installer for Windows
171
- */
172
- private async downloadPythonInstaller(): Promise<string> {
173
- // In production, this would download from python.org
174
- // For now, we assume the user needs to install manually
175
- throw new Error('Automatic installation not implemented. Please install Python manually.');
176
- }
177
-
178
- /**
179
- * Run Python installer on Windows
180
- */
181
- private async runPythonInstaller(installerPath: string): Promise<void> {
182
- await this.execAsync(`"${installerPath}" /quiet InstallAllUsers=0 PrependPath=1`);
183
- }
184
-
185
- /**
186
- * Install Python on macOS using Homebrew
187
- */
188
- private async installPythonMacOS(): Promise<string> {
189
- // Check if brew is available
190
- try {
191
- await this.execAsync('which brew');
192
- } catch {
193
- throw new Error(
194
- 'Homebrew not found. Please install Homebrew first: https://brew.sh'
195
- );
196
- }
197
-
198
- try {
199
- await this.execAsync('brew install python@3.12');
200
- return '/usr/local/bin/python3.12';
201
- } catch (error) {
202
- throw new Error(
203
- `Failed to install Python via Homebrew: ${error instanceof Error ? error.message : 'Unknown error'}`
204
- );
205
- }
206
- }
207
-
208
- /**
209
- * Install Python on Linux using apt
210
- */
211
- private async installPythonLinux(): Promise<string> {
212
- // Check if apt is available
213
- try {
214
- await this.execAsync('which apt');
215
- } catch {
216
- throw new Error(
217
- 'apt not found. Please install Python 3.10+ manually using your package manager.'
218
- );
219
- }
220
-
221
- try {
222
- await this.execAsync('sudo apt-get update && sudo apt-get install -y python3.12 python3.12-venv');
223
- return '/usr/bin/python3.12';
224
- } catch (error) {
225
- throw new Error(
226
- `Failed to install Python via apt: ${error instanceof Error ? error.message : 'Unknown error'}`
227
- );
228
- }
229
- }
230
- }