cskit-cli 1.0.21 → 1.0.23

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.
@@ -0,0 +1,170 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Python Check Command
5
+ *
6
+ * Diagnose Python installation and provide installation guidance.
7
+ */
8
+
9
+ const chalk = require('chalk');
10
+ const inquirer = require('inquirer');
11
+ const {
12
+ checkAndReport,
13
+ detectPython,
14
+ detectPip,
15
+ getInstallInstructions,
16
+ getPlatform,
17
+ canAutoInstall,
18
+ installPython,
19
+ MIN_PYTHON_VERSION
20
+ } = require('../lib/python-check');
21
+
22
+ async function pythonCommand(options) {
23
+ const python = detectPython();
24
+
25
+ if (options.json) {
26
+ // JSON output for scripting
27
+ const pip = python.found ? detectPip(python.command) : { found: false };
28
+ const autoInstall = canAutoInstall();
29
+ console.log(JSON.stringify({
30
+ platform: getPlatform(),
31
+ python: {
32
+ found: python.found,
33
+ command: python.command,
34
+ version: python.version,
35
+ path: python.path,
36
+ meetsMinimum: python.meetsMinimum,
37
+ minRequired: MIN_PYTHON_VERSION
38
+ },
39
+ pip: {
40
+ found: pip.found,
41
+ version: pip.version
42
+ },
43
+ autoInstall: {
44
+ available: autoInstall.canInstall,
45
+ manager: autoInstall.manager,
46
+ needsSudo: autoInstall.needsSudo
47
+ }
48
+ }, null, 2));
49
+ return;
50
+ }
51
+
52
+ // Auto-install Python
53
+ if (options.auto) {
54
+ if (python.found && python.meetsMinimum) {
55
+ console.log(chalk.green(`\n✓ Python ${python.version} already installed.\n`));
56
+ return;
57
+ }
58
+
59
+ const autoInfo = canAutoInstall();
60
+ if (!autoInfo.canInstall) {
61
+ console.log(chalk.red('\n✗ No supported package manager found.'));
62
+ console.log(chalk.dim(' Please install Python manually.\n'));
63
+ console.log(chalk.dim(' Run `cskit python --install` for instructions.\n'));
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log('');
68
+ console.log(chalk.cyan(`Auto-install via ${autoInfo.manager}`));
69
+ if (autoInfo.needsSudo) {
70
+ console.log(chalk.yellow('(requires sudo password)'));
71
+ }
72
+ console.log(chalk.dim(`Command: ${autoInfo.command}`));
73
+ console.log('');
74
+
75
+ const { confirm } = await inquirer.prompt([{
76
+ type: 'confirm',
77
+ name: 'confirm',
78
+ message: 'Proceed with installation?',
79
+ default: true
80
+ }]);
81
+
82
+ if (!confirm) {
83
+ console.log(chalk.dim('\nInstallation cancelled.\n'));
84
+ return;
85
+ }
86
+
87
+ console.log('');
88
+ const result = await installPython((msg) => {
89
+ console.log(chalk.dim(` ${msg}`));
90
+ });
91
+
92
+ if (result.success) {
93
+ console.log('');
94
+ console.log(chalk.green(`✓ Python ${result.version} installed successfully!`));
95
+ console.log(chalk.dim(' You may need to restart your terminal for PATH changes.'));
96
+ console.log('');
97
+ } else {
98
+ console.log('');
99
+ console.log(chalk.red(`✗ ${result.error}`));
100
+ console.log('');
101
+ process.exit(1);
102
+ }
103
+ return;
104
+ }
105
+
106
+ if (options.install) {
107
+ // Show installation instructions
108
+ const instructions = getInstallInstructions();
109
+ const autoInfo = canAutoInstall();
110
+
111
+ console.log('');
112
+ console.log(chalk.bold(`Python Installation Guide (${getPlatform()})`));
113
+ console.log(chalk.dim('─'.repeat(50)));
114
+
115
+ if (autoInfo.canInstall) {
116
+ console.log('');
117
+ console.log(chalk.green(`Auto-install available: cskit python --auto`));
118
+ console.log(chalk.dim(` Uses: ${autoInfo.manager}`));
119
+ }
120
+
121
+ console.log('');
122
+ console.log(chalk.cyan(`Manual: ${instructions.method}`));
123
+ console.log('');
124
+
125
+ for (const cmd of instructions.commands) {
126
+ if (cmd.startsWith('#')) {
127
+ console.log(chalk.dim(cmd));
128
+ } else if (cmd === '') {
129
+ console.log('');
130
+ } else {
131
+ console.log(chalk.green(` ${cmd}`));
132
+ }
133
+ }
134
+
135
+ if (instructions.notes.length > 0) {
136
+ console.log('');
137
+ console.log(chalk.bold('Notes:'));
138
+ for (const note of instructions.notes) {
139
+ console.log(chalk.dim(` • ${note}`));
140
+ }
141
+ }
142
+
143
+ console.log('');
144
+ return;
145
+ }
146
+
147
+ // Default: check and report
148
+ const result = checkAndReport();
149
+
150
+ if (result.ok) {
151
+ console.log('');
152
+ console.log(chalk.green('✓ Python environment is ready for CSK.'));
153
+ console.log('');
154
+ } else if (!python.found) {
155
+ const autoInfo = canAutoInstall();
156
+ console.log('');
157
+ if (autoInfo.canInstall) {
158
+ console.log(chalk.cyan(`Auto-install: cskit python --auto (via ${autoInfo.manager})`));
159
+ }
160
+ console.log(chalk.dim('Manual guide: cskit python --install'));
161
+ console.log('');
162
+ process.exit(1);
163
+ } else {
164
+ console.log('');
165
+ console.log(chalk.yellow('Python upgrade recommended.'));
166
+ console.log('');
167
+ }
168
+ }
169
+
170
+ module.exports = pythonCommand;
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ const initCommand = require('./commands/init');
16
16
  const authCommand = require('./commands/auth');
17
17
  const statusCommand = require('./commands/status');
18
18
  const updateCommand = require('./commands/update');
19
+ const pythonCommand = require('./commands/python');
19
20
 
20
21
  const program = new Command();
21
22
 
@@ -67,6 +68,15 @@ program
67
68
  .description('Update CSK CLI to latest version')
68
69
  .action(updateCommand);
69
70
 
71
+ // Python command - check Python environment
72
+ program
73
+ .command('python')
74
+ .description('Check Python installation status')
75
+ .option('--install', 'Show installation instructions')
76
+ .option('--auto', 'Auto-install Python via package manager')
77
+ .option('--json', 'Output as JSON for scripting')
78
+ .action(pythonCommand);
79
+
70
80
  // Parse arguments
71
81
  program.parse(process.argv);
72
82
 
package/src/lib/github.js CHANGED
@@ -266,6 +266,100 @@ async function getRepoTreeFromRef(token, ref) {
266
266
  return data.tree || [];
267
267
  }
268
268
 
269
+ /**
270
+ * Download repository as zip archive from specific ref
271
+ * @param {string} token - GitHub PAT
272
+ * @param {string} ref - Tag or branch name
273
+ * @returns {Promise<Buffer>} - Zip file buffer
274
+ */
275
+ async function downloadZipFromRef(token, ref) {
276
+ return new Promise((resolve, reject) => {
277
+ const options = {
278
+ hostname: 'api.github.com',
279
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/zipball/${ref}`,
280
+ method: 'GET',
281
+ headers: {
282
+ 'Authorization': `Bearer ${token}`,
283
+ 'Accept': 'application/vnd.github+json',
284
+ 'User-Agent': 'csk-cli',
285
+ 'X-GitHub-Api-Version': '2022-11-28'
286
+ }
287
+ };
288
+
289
+ const req = https.request(options, (res) => {
290
+ // GitHub returns 302 redirect to the actual zip URL
291
+ if (res.statusCode === 302 || res.statusCode === 301) {
292
+ const redirectUrl = new URL(res.headers.location);
293
+
294
+ // Choose http or https based on protocol
295
+ const protocol = redirectUrl.protocol === 'https:' ? https : require('http');
296
+
297
+ const redirectOptions = {
298
+ hostname: redirectUrl.hostname,
299
+ path: redirectUrl.pathname + redirectUrl.search,
300
+ method: 'GET',
301
+ headers: {
302
+ 'User-Agent': 'csk-cli'
303
+ }
304
+ };
305
+
306
+ const redirectReq = protocol.request(redirectOptions, (redirectRes) => {
307
+ // Handle another redirect (GitHub sometimes does this)
308
+ if (redirectRes.statusCode === 302 || redirectRes.statusCode === 301) {
309
+ const secondUrl = new URL(redirectRes.headers.location);
310
+ const secondProtocol = secondUrl.protocol === 'https:' ? https : require('http');
311
+
312
+ const secondOptions = {
313
+ hostname: secondUrl.hostname,
314
+ path: secondUrl.pathname + secondUrl.search,
315
+ method: 'GET',
316
+ headers: { 'User-Agent': 'csk-cli' }
317
+ };
318
+
319
+ const secondReq = secondProtocol.request(secondOptions, (secondRes) => {
320
+ if (secondRes.statusCode !== 200) {
321
+ reject(new Error(`Failed to download zip: ${secondRes.statusCode}`));
322
+ return;
323
+ }
324
+ const chunks = [];
325
+ secondRes.on('data', chunk => chunks.push(chunk));
326
+ secondRes.on('end', () => resolve(Buffer.concat(chunks)));
327
+ });
328
+ secondReq.on('error', reject);
329
+ secondReq.end();
330
+ return;
331
+ }
332
+
333
+ if (redirectRes.statusCode !== 200) {
334
+ reject(new Error(`Failed to download zip: ${redirectRes.statusCode}`));
335
+ return;
336
+ }
337
+
338
+ const chunks = [];
339
+ redirectRes.on('data', chunk => chunks.push(chunk));
340
+ redirectRes.on('end', () => resolve(Buffer.concat(chunks)));
341
+ });
342
+
343
+ redirectReq.on('error', reject);
344
+ redirectReq.end();
345
+ return;
346
+ }
347
+
348
+ if (res.statusCode !== 200) {
349
+ reject(new Error(`Failed to download zip: ${res.statusCode}`));
350
+ return;
351
+ }
352
+
353
+ const chunks = [];
354
+ res.on('data', chunk => chunks.push(chunk));
355
+ res.on('end', () => resolve(Buffer.concat(chunks)));
356
+ });
357
+
358
+ req.on('error', reject);
359
+ req.end();
360
+ });
361
+ }
362
+
269
363
  module.exports = {
270
364
  verifyAccess,
271
365
  getRepoTree,
@@ -273,6 +367,7 @@ module.exports = {
273
367
  getFileContent,
274
368
  downloadFile,
275
369
  downloadFileFromRef,
370
+ downloadZipFromRef,
276
371
  getLatestRelease,
277
372
  getAllReleases
278
373
  };
@@ -0,0 +1,402 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Python Detection and Installation Helper
5
+ *
6
+ * Detects Python availability, version, and provides
7
+ * platform-specific installation guidance.
8
+ */
9
+
10
+ const { execSync, spawnSync } = require('child_process');
11
+ const os = require('os');
12
+ const chalk = require('chalk');
13
+
14
+ // Minimum Python version required
15
+ const MIN_PYTHON_VERSION = '3.8';
16
+
17
+ /**
18
+ * Detect current platform
19
+ * @returns {'windows'|'macos'|'linux'}
20
+ */
21
+ function getPlatform() {
22
+ const platform = os.platform();
23
+ if (platform === 'win32') return 'windows';
24
+ if (platform === 'darwin') return 'macos';
25
+ return 'linux';
26
+ }
27
+
28
+ /**
29
+ * Try to find Python executable
30
+ * @returns {{found: boolean, command: string|null, version: string|null, path: string|null}}
31
+ */
32
+ function detectPython() {
33
+ const platform = getPlatform();
34
+
35
+ // Commands to try in order of preference
36
+ const commands = platform === 'windows'
37
+ ? ['python', 'python3', 'py -3', 'py']
38
+ : ['python3', 'python'];
39
+
40
+ for (const cmd of commands) {
41
+ try {
42
+ // Get version
43
+ const versionResult = spawnSync(
44
+ cmd.split(' ')[0],
45
+ [...cmd.split(' ').slice(1), '--version'],
46
+ { encoding: 'utf-8', shell: true, timeout: 5000 }
47
+ );
48
+
49
+ if (versionResult.status === 0) {
50
+ const output = (versionResult.stdout || versionResult.stderr || '').trim();
51
+ const match = output.match(/Python (\d+\.\d+\.\d+)/i);
52
+
53
+ if (match) {
54
+ const version = match[1];
55
+
56
+ // Get path
57
+ let pythonPath = null;
58
+ try {
59
+ const pathCmd = platform === 'windows'
60
+ ? `${cmd} -c "import sys; print(sys.executable)"`
61
+ : `${cmd} -c 'import sys; print(sys.executable)'`;
62
+
63
+ pythonPath = execSync(pathCmd, { encoding: 'utf-8', timeout: 5000 }).trim();
64
+ } catch (e) {
65
+ // Ignore path detection errors
66
+ }
67
+
68
+ return {
69
+ found: true,
70
+ command: cmd,
71
+ version,
72
+ path: pythonPath,
73
+ meetsMinimum: compareVersions(version, MIN_PYTHON_VERSION) >= 0
74
+ };
75
+ }
76
+ }
77
+ } catch (e) {
78
+ // Continue to next command
79
+ }
80
+ }
81
+
82
+ return { found: false, command: null, version: null, path: null, meetsMinimum: false };
83
+ }
84
+
85
+ /**
86
+ * Compare semantic versions
87
+ * @param {string} v1
88
+ * @param {string} v2
89
+ * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
90
+ */
91
+ function compareVersions(v1, v2) {
92
+ const parts1 = v1.split('.').map(Number);
93
+ const parts2 = v2.split('.').map(Number);
94
+
95
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
96
+ const p1 = parts1[i] || 0;
97
+ const p2 = parts2[i] || 0;
98
+ if (p1 < p2) return -1;
99
+ if (p1 > p2) return 1;
100
+ }
101
+ return 0;
102
+ }
103
+
104
+ /**
105
+ * Check if pip is available
106
+ * @param {string} pythonCmd - Python command to use
107
+ * @returns {{found: boolean, version: string|null}}
108
+ */
109
+ function detectPip(pythonCmd) {
110
+ try {
111
+ const result = spawnSync(
112
+ pythonCmd.split(' ')[0],
113
+ [...pythonCmd.split(' ').slice(1), '-m', 'pip', '--version'],
114
+ { encoding: 'utf-8', shell: true, timeout: 5000 }
115
+ );
116
+
117
+ if (result.status === 0) {
118
+ const match = (result.stdout || '').match(/pip (\d+\.\d+)/);
119
+ return { found: true, version: match ? match[1] : 'unknown' };
120
+ }
121
+ } catch (e) {
122
+ // Ignore
123
+ }
124
+
125
+ return { found: false, version: null };
126
+ }
127
+
128
+ /**
129
+ * Get installation instructions for current platform
130
+ * @returns {{method: string, commands: string[], notes: string[]}}
131
+ */
132
+ function getInstallInstructions() {
133
+ const platform = getPlatform();
134
+
135
+ switch (platform) {
136
+ case 'windows':
137
+ return {
138
+ method: 'Microsoft Store or python.org',
139
+ commands: [
140
+ '# Option 1: Microsoft Store (recommended)',
141
+ 'winget install Python.Python.3.12',
142
+ '',
143
+ '# Option 2: Download from python.org',
144
+ '# https://www.python.org/downloads/windows/',
145
+ '',
146
+ '# After install, restart terminal and run:',
147
+ 'python --version'
148
+ ],
149
+ notes: [
150
+ 'Check "Add Python to PATH" during installation',
151
+ 'Restart terminal after installation'
152
+ ]
153
+ };
154
+
155
+ case 'macos':
156
+ return {
157
+ method: 'Homebrew or python.org',
158
+ commands: [
159
+ '# Option 1: Homebrew (recommended)',
160
+ 'brew install python@3.12',
161
+ '',
162
+ '# Option 2: Download from python.org',
163
+ '# https://www.python.org/downloads/macos/',
164
+ '',
165
+ '# Option 3: Xcode Command Line Tools',
166
+ 'xcode-select --install'
167
+ ],
168
+ notes: [
169
+ 'Homebrew: https://brew.sh if not installed',
170
+ 'After install: python3 --version'
171
+ ]
172
+ };
173
+
174
+ case 'linux':
175
+ return {
176
+ method: 'Package manager',
177
+ commands: [
178
+ '# Debian/Ubuntu:',
179
+ 'sudo apt update && sudo apt install python3 python3-pip',
180
+ '',
181
+ '# Fedora:',
182
+ 'sudo dnf install python3 python3-pip',
183
+ '',
184
+ '# Arch:',
185
+ 'sudo pacman -S python python-pip'
186
+ ],
187
+ notes: [
188
+ 'Most Linux distros have Python pre-installed',
189
+ 'Verify with: python3 --version'
190
+ ]
191
+ };
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Display Python status and instructions
197
+ * @returns {{ok: boolean, pythonCmd: string|null}}
198
+ */
199
+ function checkAndReport() {
200
+ const python = detectPython();
201
+ const platform = getPlatform();
202
+
203
+ console.log('');
204
+ console.log(chalk.bold('Python Environment Check'));
205
+ console.log(chalk.dim('─'.repeat(40)));
206
+ console.log(`Platform: ${chalk.cyan(platform)}`);
207
+
208
+ if (python.found) {
209
+ const versionColor = python.meetsMinimum ? chalk.green : chalk.yellow;
210
+ console.log(`Python: ${versionColor(python.version)} (${python.command})`);
211
+
212
+ if (python.path) {
213
+ console.log(`Path: ${chalk.dim(python.path)}`);
214
+ }
215
+
216
+ if (!python.meetsMinimum) {
217
+ console.log('');
218
+ console.log(chalk.yellow(`⚠ Python ${MIN_PYTHON_VERSION}+ required, found ${python.version}`));
219
+ console.log(chalk.dim(' Some features may not work correctly'));
220
+ }
221
+
222
+ // Check pip
223
+ const pip = detectPip(python.command);
224
+ if (pip.found) {
225
+ console.log(`pip: ${chalk.green(pip.version)}`);
226
+ } else {
227
+ console.log(`pip: ${chalk.yellow('not found')}`);
228
+ console.log(chalk.dim(` Install: ${python.command} -m ensurepip --upgrade`));
229
+ }
230
+
231
+ console.log(chalk.dim('─'.repeat(40)));
232
+ return { ok: python.meetsMinimum, pythonCmd: python.command };
233
+ }
234
+
235
+ // Python not found
236
+ console.log(`Python: ${chalk.red('not found')}`);
237
+ console.log('');
238
+ console.log(chalk.yellow('Python is required for advanced CSK features:'));
239
+ console.log(chalk.dim(' - Data fetching and analysis'));
240
+ console.log(chalk.dim(' - Chart generation'));
241
+ console.log(chalk.dim(' - MCP server tools'));
242
+ console.log('');
243
+
244
+ const instructions = getInstallInstructions();
245
+ console.log(chalk.bold(`Install via ${instructions.method}:`));
246
+ console.log('');
247
+
248
+ for (const cmd of instructions.commands) {
249
+ if (cmd.startsWith('#')) {
250
+ console.log(chalk.dim(cmd));
251
+ } else if (cmd === '') {
252
+ console.log('');
253
+ } else {
254
+ console.log(chalk.cyan(` ${cmd}`));
255
+ }
256
+ }
257
+
258
+ if (instructions.notes.length > 0) {
259
+ console.log('');
260
+ console.log(chalk.dim('Notes:'));
261
+ for (const note of instructions.notes) {
262
+ console.log(chalk.dim(` • ${note}`));
263
+ }
264
+ }
265
+
266
+ console.log(chalk.dim('─'.repeat(40)));
267
+ return { ok: false, pythonCmd: null };
268
+ }
269
+
270
+ /**
271
+ * Quick check - returns Python command or null
272
+ * @returns {string|null}
273
+ */
274
+ function quickCheck() {
275
+ const python = detectPython();
276
+ return python.found && python.meetsMinimum ? python.command : null;
277
+ }
278
+
279
+ /**
280
+ * Check if a package manager is available
281
+ * @returns {{available: boolean, manager: string|null, installCmd: string|null}}
282
+ */
283
+ function detectPackageManager() {
284
+ const platform = getPlatform();
285
+
286
+ const managers = {
287
+ windows: [
288
+ { name: 'winget', check: 'winget --version', install: 'winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements' },
289
+ { name: 'choco', check: 'choco --version', install: 'choco install python3 -y' },
290
+ { name: 'scoop', check: 'scoop --version', install: 'scoop install python' }
291
+ ],
292
+ macos: [
293
+ { name: 'brew', check: 'brew --version', install: 'brew install python@3.12' }
294
+ ],
295
+ linux: [
296
+ { name: 'apt', check: 'apt --version', install: 'sudo apt update && sudo apt install -y python3 python3-pip python3-venv' },
297
+ { name: 'dnf', check: 'dnf --version', install: 'sudo dnf install -y python3 python3-pip' },
298
+ { name: 'pacman', check: 'pacman --version', install: 'sudo pacman -S --noconfirm python python-pip' },
299
+ { name: 'apk', check: 'apk --version', install: 'apk add python3 py3-pip' }
300
+ ]
301
+ };
302
+
303
+ const platformManagers = managers[platform] || [];
304
+
305
+ for (const mgr of platformManagers) {
306
+ try {
307
+ const result = spawnSync(mgr.check.split(' ')[0], mgr.check.split(' ').slice(1), {
308
+ encoding: 'utf-8',
309
+ shell: true,
310
+ timeout: 5000,
311
+ stdio: 'pipe'
312
+ });
313
+
314
+ if (result.status === 0) {
315
+ return {
316
+ available: true,
317
+ manager: mgr.name,
318
+ installCmd: mgr.install
319
+ };
320
+ }
321
+ } catch (e) {
322
+ // Continue to next manager
323
+ }
324
+ }
325
+
326
+ return { available: false, manager: null, installCmd: null };
327
+ }
328
+
329
+ /**
330
+ * Attempt to install Python using detected package manager
331
+ * @param {function} onProgress - Callback for progress updates
332
+ * @returns {Promise<{success: boolean, error?: string}>}
333
+ */
334
+ async function installPython(onProgress = console.log) {
335
+ const pkgMgr = detectPackageManager();
336
+
337
+ if (!pkgMgr.available) {
338
+ return {
339
+ success: false,
340
+ error: 'No supported package manager found. Please install Python manually.'
341
+ };
342
+ }
343
+
344
+ onProgress(`Installing Python via ${pkgMgr.manager}...`);
345
+ onProgress(`Command: ${pkgMgr.installCmd}`);
346
+
347
+ try {
348
+ // Execute install command
349
+ execSync(pkgMgr.installCmd, {
350
+ stdio: 'inherit', // Show output to user
351
+ shell: true,
352
+ timeout: 300000 // 5 minute timeout
353
+ });
354
+
355
+ // Verify installation
356
+ const python = detectPython();
357
+ if (python.found) {
358
+ onProgress(`Python ${python.version} installed successfully!`);
359
+ return { success: true, version: python.version, command: python.command };
360
+ } else {
361
+ return {
362
+ success: false,
363
+ error: 'Installation completed but Python not detected. You may need to restart your terminal.'
364
+ };
365
+ }
366
+ } catch (error) {
367
+ return {
368
+ success: false,
369
+ error: `Installation failed: ${error.message}`
370
+ };
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Check if auto-install is possible
376
+ * @returns {{canInstall: boolean, manager: string|null, command: string|null, needsSudo: boolean}}
377
+ */
378
+ function canAutoInstall() {
379
+ const pkgMgr = detectPackageManager();
380
+ const platform = getPlatform();
381
+
382
+ return {
383
+ canInstall: pkgMgr.available,
384
+ manager: pkgMgr.manager,
385
+ command: pkgMgr.installCmd,
386
+ needsSudo: platform === 'linux' && pkgMgr.installCmd?.includes('sudo')
387
+ };
388
+ }
389
+
390
+ module.exports = {
391
+ getPlatform,
392
+ detectPython,
393
+ detectPip,
394
+ compareVersions,
395
+ getInstallInstructions,
396
+ checkAndReport,
397
+ quickCheck,
398
+ detectPackageManager,
399
+ installPython,
400
+ canAutoInstall,
401
+ MIN_PYTHON_VERSION
402
+ };