@allanpk716/work-skills-setup 0.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/bin/setup.js +10 -0
- package/package.json +38 -0
- package/src/cli.js +44 -0
- package/src/configurators/git-ssh.js +84 -0
- package/src/configurators/git-user.js +78 -0
- package/src/configurators/index.js +78 -0
- package/src/configurators/pushover.js +195 -0
- package/src/detectors/git.js +37 -0
- package/src/detectors/index.js +56 -0
- package/src/detectors/pip-package.js +43 -0
- package/src/detectors/python.js +48 -0
- package/src/detectors/ssh-tools.js +94 -0
- package/src/i18n/en.json +112 -0
- package/src/i18n/index.js +70 -0
- package/src/i18n/zh.json +112 -0
- package/src/index.js +61 -0
- package/src/installers/index.js +92 -0
- package/src/installers/pip-installer.js +63 -0
- package/src/marketplace/config-manager.js +87 -0
- package/src/marketplace/index.js +157 -0
- package/src/marketplace/plugin-discovery.js +68 -0
- package/src/marketplace/plugin-installer.js +144 -0
- package/src/platform.js +34 -0
- package/src/verification/formatter.js +64 -0
- package/src/verification/index.js +79 -0
- package/src/verification/parser.js +53 -0
- package/src/verification/runner.js +72 -0
- package/src/welcome.js +50 -0
package/bin/setup.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@allanpk716/work-skills-setup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Work Skills installer for Windows developers",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"work-skills-setup": "./bin/setup.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=16.0.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude-code",
|
|
21
|
+
"work-skills",
|
|
22
|
+
"installer",
|
|
23
|
+
"windows"
|
|
24
|
+
],
|
|
25
|
+
"author": "allanpk716",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"boxen": "^5.1.2",
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"commander": "^14.0.3",
|
|
31
|
+
"enquirer": "^2.4.1",
|
|
32
|
+
"execa": "^5.1.1",
|
|
33
|
+
"winreg": "^1.2.5"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"jest": "^30.3.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const { setLanguage } = require('./i18n/index.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parse command line arguments
|
|
8
|
+
* @param {string[]} argv - Process arguments (usually process.argv)
|
|
9
|
+
* @returns {Object} Parsed options
|
|
10
|
+
*/
|
|
11
|
+
function parseArgs(argv = process.argv) {
|
|
12
|
+
const packageJson = require('../package.json');
|
|
13
|
+
|
|
14
|
+
// Create a new Command instance for each call (avoids singleton issues in tests)
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('work-skills-setup')
|
|
19
|
+
.description('Work Skills Setup - Claude Code skills installer for Windows developers')
|
|
20
|
+
.version(packageJson.version, '-v, --version', 'Show version')
|
|
21
|
+
.option('-l, --lang <locale>', 'Language (en/zh)', 'auto')
|
|
22
|
+
.option('--no-color', 'Disable colored output')
|
|
23
|
+
.option('--verify', 'Run installation verification only')
|
|
24
|
+
.allowExcessArguments(true)
|
|
25
|
+
.exitOverride() // Prevent process.exit in tests
|
|
26
|
+
.parse(argv);
|
|
27
|
+
|
|
28
|
+
const options = program.opts();
|
|
29
|
+
|
|
30
|
+
// Set language if specified
|
|
31
|
+
if (options.lang && options.lang !== 'auto') {
|
|
32
|
+
setLanguage(options.lang);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
lang: options.lang,
|
|
37
|
+
useColors: options.color !== false,
|
|
38
|
+
verifyOnly: options.verify === true
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = {
|
|
43
|
+
parseArgs
|
|
44
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Confirm } = require('enquirer');
|
|
4
|
+
const execa = require('execa');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { t } = require('../i18n/index.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect Git SSH configuration
|
|
10
|
+
* @returns {Promise<{configured: boolean, command: string|null}>}
|
|
11
|
+
*/
|
|
12
|
+
async function detectGitSSH() {
|
|
13
|
+
try {
|
|
14
|
+
const { stdout } = await execa('git', ['config', '--get', 'core.sshCommand'], { reject: false });
|
|
15
|
+
|
|
16
|
+
if (stdout && stdout.trim()) {
|
|
17
|
+
return {
|
|
18
|
+
configured: true,
|
|
19
|
+
command: stdout.trim()
|
|
20
|
+
};
|
|
21
|
+
} else {
|
|
22
|
+
return {
|
|
23
|
+
configured: false,
|
|
24
|
+
command: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
configured: false,
|
|
30
|
+
command: null
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configure Git SSH through interactive prompts and guidance
|
|
37
|
+
* @returns {Promise<{status: string, details: string}>}
|
|
38
|
+
*/
|
|
39
|
+
async function configureGitSSH() {
|
|
40
|
+
const current = await detectGitSSH();
|
|
41
|
+
|
|
42
|
+
// If already configured
|
|
43
|
+
if (current.configured) {
|
|
44
|
+
console.log(chalk.green(t('gitSSH.configured')));
|
|
45
|
+
console.log(chalk.gray(` ${current.command}`));
|
|
46
|
+
return { status: 'configured', details: current.command };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Show guidance
|
|
50
|
+
console.log(chalk.yellow(t('gitSSH.notConfigured')));
|
|
51
|
+
console.log(chalk.gray(t('gitSSH.recommended')));
|
|
52
|
+
console.log('');
|
|
53
|
+
console.log(chalk.bold(t('gitSSH.guidance')));
|
|
54
|
+
console.log(chalk.gray(t('gitSSH.step1')));
|
|
55
|
+
console.log(chalk.gray(t('gitSSH.step2')));
|
|
56
|
+
console.log(chalk.gray(t('gitSSH.step3')));
|
|
57
|
+
console.log(chalk.gray(t('gitSSH.step4')));
|
|
58
|
+
console.log(chalk.cyan(t('gitSSH.command')));
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log(chalk.gray(t('gitSSH.docs')));
|
|
61
|
+
console.log('');
|
|
62
|
+
|
|
63
|
+
// Ask if user wants to skip
|
|
64
|
+
const skipPrompt = new Confirm({
|
|
65
|
+
name: 'skip',
|
|
66
|
+
message: t('gitSSH.promptSkip'),
|
|
67
|
+
initial: true
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const shouldSkip = await skipPrompt.run();
|
|
71
|
+
|
|
72
|
+
if (shouldSkip) {
|
|
73
|
+
console.log(chalk.yellow(t('gitSSH.skipped')));
|
|
74
|
+
return { status: 'skipped', details: 'user skipped' };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// User will configure manually
|
|
78
|
+
return { status: 'configured', details: 'guidance shown' };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
detectGitSSH,
|
|
83
|
+
configureGitSSH
|
|
84
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Input } = require('enquirer');
|
|
4
|
+
const execa = require('execa');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { t } = require('../i18n/index.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect Git user.name and user.email configuration
|
|
10
|
+
* @returns {Promise<{name: string|null, email: string|null}>}
|
|
11
|
+
*/
|
|
12
|
+
async function detectGitUser() {
|
|
13
|
+
try {
|
|
14
|
+
const { stdout: name } = await execa('git', ['config', '--global', '--get', 'user.name'], { reject: false });
|
|
15
|
+
const { stdout: email } = await execa('git', ['config', '--global', '--get', 'user.email'], { reject: false });
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
name: name || null,
|
|
19
|
+
email: email || null
|
|
20
|
+
};
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return {
|
|
23
|
+
name: null,
|
|
24
|
+
email: null
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configure Git user information through interactive prompts
|
|
31
|
+
* @returns {Promise<{status: string, name?: string, email?: string}>}
|
|
32
|
+
*/
|
|
33
|
+
async function configureGitUser() {
|
|
34
|
+
const current = await detectGitUser();
|
|
35
|
+
|
|
36
|
+
// If already configured
|
|
37
|
+
if (current.name && current.email) {
|
|
38
|
+
console.log(chalk.green(t('gitUser.alreadyConfigured')));
|
|
39
|
+
console.log(chalk.gray(` user.name: ${current.name}`));
|
|
40
|
+
console.log(chalk.gray(` user.email: ${current.email}`));
|
|
41
|
+
return { status: 'configured', name: current.name, email: current.email };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Show requirement
|
|
45
|
+
console.log(chalk.yellow(t('gitUser.required')));
|
|
46
|
+
|
|
47
|
+
// Prompt for user.name
|
|
48
|
+
const namePrompt = new Input({
|
|
49
|
+
name: 'userName',
|
|
50
|
+
message: t('gitUser.promptName')
|
|
51
|
+
});
|
|
52
|
+
const userName = await namePrompt.run();
|
|
53
|
+
|
|
54
|
+
// Prompt for user.email
|
|
55
|
+
const emailPrompt = new Input({
|
|
56
|
+
name: 'userEmail',
|
|
57
|
+
message: t('gitUser.promptEmail')
|
|
58
|
+
});
|
|
59
|
+
const userEmail = await emailPrompt.run();
|
|
60
|
+
|
|
61
|
+
// Set Git config
|
|
62
|
+
try {
|
|
63
|
+
await execa('git', ['config', '--global', 'user.name', userName]);
|
|
64
|
+
await execa('git', ['config', '--global', 'user.email', userEmail]);
|
|
65
|
+
|
|
66
|
+
console.log(chalk.green(t('gitUser.configured')));
|
|
67
|
+
return { status: 'configured', name: userName, email: userEmail };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.log(chalk.red(t('gitUser.failed')));
|
|
70
|
+
console.log(chalk.gray(error.message));
|
|
71
|
+
return { status: 'failed' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
detectGitUser,
|
|
77
|
+
configureGitUser
|
|
78
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { t } = require('../i18n/index.js');
|
|
5
|
+
const { configurePushover } = require('./pushover.js');
|
|
6
|
+
const { configureGitSSH } = require('./git-ssh.js');
|
|
7
|
+
const { configureGitUser } = require('./git-user.js');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display configuration summary table
|
|
11
|
+
* @param {Array<{name: string, status: string, details?: string}>} results
|
|
12
|
+
*/
|
|
13
|
+
function displayConfigSummary(results) {
|
|
14
|
+
console.log('\n' + chalk.bold(t('config.summary')));
|
|
15
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
16
|
+
|
|
17
|
+
results.forEach(result => {
|
|
18
|
+
let statusIcon, statusColor;
|
|
19
|
+
|
|
20
|
+
switch (result.status) {
|
|
21
|
+
case 'configured':
|
|
22
|
+
statusIcon = '✓';
|
|
23
|
+
statusColor = chalk.green;
|
|
24
|
+
break;
|
|
25
|
+
case 'skipped':
|
|
26
|
+
statusIcon = '⊘';
|
|
27
|
+
statusColor = chalk.yellow;
|
|
28
|
+
break;
|
|
29
|
+
case 'failed':
|
|
30
|
+
statusIcon = '✗';
|
|
31
|
+
statusColor = chalk.red;
|
|
32
|
+
break;
|
|
33
|
+
default:
|
|
34
|
+
statusIcon = '?';
|
|
35
|
+
statusColor = chalk.gray;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const statusText = t(`config.status.${result.status}`);
|
|
39
|
+
const details = result.details ? ` (${result.details})` : '';
|
|
40
|
+
console.log(`${statusColor(statusIcon)} ${result.name.padEnd(20)} ${statusColor(statusText)} ${chalk.gray(details)}`);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(chalk.gray('─'.repeat(60)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Run all configurators in sequence
|
|
48
|
+
* @returns {Promise<void>}
|
|
49
|
+
*/
|
|
50
|
+
async function runAllConfigurators() {
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
// 1. Pushover (optional)
|
|
54
|
+
console.log(chalk.bold('\n' + t('config.section.pushover')));
|
|
55
|
+
const pushoverResult = await configurePushover();
|
|
56
|
+
results.push({ name: 'Pushover', status: pushoverResult.status, details: pushoverResult.details });
|
|
57
|
+
|
|
58
|
+
// 2. Git SSH (optional)
|
|
59
|
+
console.log(chalk.bold('\n' + t('config.section.gitSSH')));
|
|
60
|
+
const sshResult = await configureGitSSH();
|
|
61
|
+
results.push({ name: 'Git SSH', status: sshResult.status, details: sshResult.details });
|
|
62
|
+
|
|
63
|
+
// 3. Git user.info (required)
|
|
64
|
+
console.log(chalk.bold('\n' + t('config.section.gitUser')));
|
|
65
|
+
const userResult = await configureGitUser();
|
|
66
|
+
results.push(
|
|
67
|
+
{ name: 'Git user.name', status: userResult.status, details: userResult.name },
|
|
68
|
+
{ name: 'Git user.email', status: userResult.status, details: userResult.email }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Display summary
|
|
72
|
+
displayConfigSummary(results);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
runAllConfigurators,
|
|
77
|
+
displayConfigSummary
|
|
78
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Input, Confirm } = require('enquirer');
|
|
4
|
+
const execa = require('execa');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { t } = require('../i18n/index.js');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect existing Pushover environment variables
|
|
10
|
+
* @returns {{token: string|null, user: string|null}}
|
|
11
|
+
*/
|
|
12
|
+
function detectPushoverEnv() {
|
|
13
|
+
return {
|
|
14
|
+
token: process.env.PUSHOVER_TOKEN || null,
|
|
15
|
+
user: process.env.PUSHOVER_USER || null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set environment variable using setx command
|
|
21
|
+
* @param {string} name - Variable name
|
|
22
|
+
* @param {string} value - Variable value
|
|
23
|
+
* @returns {Promise<{success: boolean, error?: string, errorDetails?: string}>}
|
|
24
|
+
*/
|
|
25
|
+
async function setEnvVariable(name, value) {
|
|
26
|
+
try {
|
|
27
|
+
await execa('setx', [name, value]);
|
|
28
|
+
return { success: true };
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const stderr = error.stderr || error.message || '';
|
|
31
|
+
let errorType = 'unknown';
|
|
32
|
+
|
|
33
|
+
// Detect error type from stderr
|
|
34
|
+
if (stderr.includes('Access is denied')) {
|
|
35
|
+
errorType = 'permission';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: errorType,
|
|
41
|
+
errorDetails: stderr
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Validate Pushover credentials against API
|
|
48
|
+
* @param {string} token - Pushover application token
|
|
49
|
+
* @param {string} user - Pushover user key
|
|
50
|
+
* @returns {Promise<{valid: boolean, devices?: Array, error?: string}>}
|
|
51
|
+
*/
|
|
52
|
+
async function validatePushoverCredentials(token, user) {
|
|
53
|
+
try {
|
|
54
|
+
const { stdout } = await execa('curl', [
|
|
55
|
+
'-s',
|
|
56
|
+
'-X',
|
|
57
|
+
'POST',
|
|
58
|
+
`https://api.pushover.net/1/users/validate.json`,
|
|
59
|
+
'-d',
|
|
60
|
+
`token=${token}`,
|
|
61
|
+
'-d',
|
|
62
|
+
`user=${user}`
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const response = JSON.parse(stdout);
|
|
66
|
+
|
|
67
|
+
if (response.status === 1) {
|
|
68
|
+
return {
|
|
69
|
+
valid: true,
|
|
70
|
+
devices: response.devices || []
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
return {
|
|
74
|
+
valid: false,
|
|
75
|
+
error: response.errors?.join(', ') || 'Invalid credentials'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
valid: false,
|
|
81
|
+
error: error.message || 'Validation failed'
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Configure Pushover through interactive prompts
|
|
88
|
+
* @param {number} maxRetries - Maximum retry attempts (default: 3)
|
|
89
|
+
* @returns {Promise<{status: string, details: string}>}
|
|
90
|
+
*/
|
|
91
|
+
async function configurePushover(maxRetries = 3) {
|
|
92
|
+
const currentEnv = detectPushoverEnv();
|
|
93
|
+
|
|
94
|
+
// Check if already configured
|
|
95
|
+
if (currentEnv.token && currentEnv.user) {
|
|
96
|
+
console.log(chalk.green(t('pushover.alreadyConfigured')));
|
|
97
|
+
console.log(chalk.gray(` Token: ${currentEnv.token.substring(0, 8)}...`));
|
|
98
|
+
console.log(chalk.gray(` User: ${currentEnv.user.substring(0, 8)}...`));
|
|
99
|
+
|
|
100
|
+
const reconfigurePrompt = new Confirm({
|
|
101
|
+
name: 'reconfigure',
|
|
102
|
+
message: t('pushover.promptReconfigure'),
|
|
103
|
+
initial: false
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const shouldReconfigure = await reconfigurePrompt.run();
|
|
107
|
+
if (!shouldReconfigure) {
|
|
108
|
+
return { status: 'configured', details: 'already set' };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Ask if user wants to configure Pushover
|
|
113
|
+
const configurePrompt = new Confirm({
|
|
114
|
+
name: 'configure',
|
|
115
|
+
message: t('pushover.promptConfigure'),
|
|
116
|
+
initial: true
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const shouldConfigure = await configurePrompt.run();
|
|
120
|
+
|
|
121
|
+
if (!shouldConfigure) {
|
|
122
|
+
console.log(chalk.yellow(t('pushover.skipped')));
|
|
123
|
+
return { status: 'skipped', details: 'user skipped' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Retry loop
|
|
127
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
128
|
+
// Prompt for token
|
|
129
|
+
const tokenPrompt = new Input({
|
|
130
|
+
name: 'token',
|
|
131
|
+
message: t('pushover.promptToken')
|
|
132
|
+
});
|
|
133
|
+
const token = await tokenPrompt.run();
|
|
134
|
+
|
|
135
|
+
// Prompt for user
|
|
136
|
+
const userPrompt = new Input({
|
|
137
|
+
name: 'user',
|
|
138
|
+
message: t('pushover.promptUser')
|
|
139
|
+
});
|
|
140
|
+
const user = await userPrompt.run();
|
|
141
|
+
|
|
142
|
+
// Validate credentials
|
|
143
|
+
console.log(chalk.gray(t('pushover.validating')));
|
|
144
|
+
const validation = await validatePushoverCredentials(token, user);
|
|
145
|
+
|
|
146
|
+
if (!validation.valid) {
|
|
147
|
+
console.log(chalk.red(t('pushover.validationFailed')));
|
|
148
|
+
console.log(chalk.gray(` -> ${validation.error}`));
|
|
149
|
+
|
|
150
|
+
if (attempt < maxRetries) {
|
|
151
|
+
console.log(chalk.yellow(t('pushover.retryPrompt', { attempt, max: maxRetries })));
|
|
152
|
+
continue;
|
|
153
|
+
} else {
|
|
154
|
+
return { status: 'failed', details: validation.error };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Save credentials
|
|
159
|
+
console.log(chalk.gray(t('pushover.saving')));
|
|
160
|
+
|
|
161
|
+
const tokenResult = await setEnvVariable('PUSHOVER_TOKEN', token);
|
|
162
|
+
if (!tokenResult.success) {
|
|
163
|
+
console.log(chalk.red(t('pushover.saveFailed')));
|
|
164
|
+
console.log(chalk.gray(t('guidance.pushoverManual')));
|
|
165
|
+
return { status: 'failed', details: 'setx failed for token' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const userResult = await setEnvVariable('PUSHOVER_USER', user);
|
|
169
|
+
if (!userResult.success) {
|
|
170
|
+
console.log(chalk.red(t('pushover.saveFailed')));
|
|
171
|
+
console.log(chalk.gray(t('guidance.pushoverManual')));
|
|
172
|
+
return { status: 'failed', details: 'setx failed for user' };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Set environment variables for current process (immediate effect)
|
|
176
|
+
process.env.PUSHOVER_TOKEN = token;
|
|
177
|
+
process.env.PUSHOVER_USER = user;
|
|
178
|
+
|
|
179
|
+
// Success
|
|
180
|
+
console.log(chalk.green(t('pushover.configured')));
|
|
181
|
+
console.log(chalk.gray(t('pushover.currentSessionSet')));
|
|
182
|
+
console.log(chalk.yellow(t('pushover.restartReminder')));
|
|
183
|
+
return { status: 'configured', details: 'validated and saved' };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Should not reach here, but just in case
|
|
187
|
+
return { status: 'failed', details: 'max retries exceeded' };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
detectPushoverEnv,
|
|
192
|
+
setEnvVariable,
|
|
193
|
+
validatePushoverCredentials,
|
|
194
|
+
configurePushover
|
|
195
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const execa = require('execa');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect Git installation and version
|
|
7
|
+
* @returns {Promise<{name: string, installed: boolean, version: string|null, meetsMinimum: boolean, command: string|null, guidance: string}>}
|
|
8
|
+
*/
|
|
9
|
+
async function detectGit() {
|
|
10
|
+
try {
|
|
11
|
+
const { stdout } = await execa('git', ['--version']);
|
|
12
|
+
|
|
13
|
+
// stdout: "git version 2.43.0.windows.1"
|
|
14
|
+
const versionMatch = stdout.match(/git version (\d+\.\d+\.\d+)/i);
|
|
15
|
+
const version = versionMatch ? versionMatch[1] : null;
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
name: 'Git',
|
|
19
|
+
command: 'git',
|
|
20
|
+
installed: true,
|
|
21
|
+
version,
|
|
22
|
+
meetsMinimum: true, // Any Git version is acceptable
|
|
23
|
+
guidance: 'guidance.installGit'
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return {
|
|
27
|
+
name: 'Git',
|
|
28
|
+
command: null,
|
|
29
|
+
installed: false,
|
|
30
|
+
version: null,
|
|
31
|
+
meetsMinimum: false,
|
|
32
|
+
guidance: 'guidance.installGit'
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { detectGit };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const { t } = require('../i18n/index.js');
|
|
5
|
+
const { detectPython } = require('./python.js');
|
|
6
|
+
const { detectGit } = require('./git.js');
|
|
7
|
+
const { detectSSHTools } = require('./ssh-tools.js');
|
|
8
|
+
const { detectPipPackage } = require('./pip-package.js');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Print detection result with status indicator
|
|
12
|
+
* @param {Object} result - Detection result object
|
|
13
|
+
*/
|
|
14
|
+
function printResult(result) {
|
|
15
|
+
const status = result.installed && result.meetsMinimum !== false;
|
|
16
|
+
const symbol = status ? chalk.green('[OK]') : chalk.red('[FAIL]');
|
|
17
|
+
const version = result.version ? ` (${result.version})` : '';
|
|
18
|
+
|
|
19
|
+
console.log(` ${symbol} ${result.name}${version}`);
|
|
20
|
+
|
|
21
|
+
if (!status && result.guidance) {
|
|
22
|
+
console.log(chalk.gray(` -> ${t(result.guidance)}`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run all environment detectors
|
|
28
|
+
* @returns {Promise<{results: Array, allPassed: boolean}>}
|
|
29
|
+
*/
|
|
30
|
+
async function runAllDetectors() {
|
|
31
|
+
console.log('\n' + t('detection.checking') + '\n');
|
|
32
|
+
|
|
33
|
+
const results = await Promise.all([
|
|
34
|
+
detectPython(),
|
|
35
|
+
detectGit(),
|
|
36
|
+
detectSSHTools(),
|
|
37
|
+
detectPipPackage('requests')
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
results.forEach(printResult);
|
|
41
|
+
|
|
42
|
+
const failedCount = results.filter(r => !r.installed || r.meetsMinimum === false).length;
|
|
43
|
+
const passedCount = results.length - failedCount;
|
|
44
|
+
|
|
45
|
+
console.log('\n' + t('detection.summary', { passed: passedCount, total: results.length }));
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
results,
|
|
49
|
+
allPassed: failedCount === 0
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
runAllDetectors,
|
|
55
|
+
printResult
|
|
56
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const execa = require('execa');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Detect if a Python pip package is installed
|
|
7
|
+
* @param {string} packageName - Name of the package to detect
|
|
8
|
+
* @param {string} pythonCmd - Python command to use (default: 'python')
|
|
9
|
+
* @returns {Promise<{name: string, installed: boolean, version: string|null, meetsMinimum: boolean, guidance: string}>}
|
|
10
|
+
*/
|
|
11
|
+
async function detectPipPackage(packageName, pythonCmd = 'python') {
|
|
12
|
+
try {
|
|
13
|
+
const { stdout } = await execa(pythonCmd, ['-m', 'pip', 'show', packageName]);
|
|
14
|
+
|
|
15
|
+
// Parse pip show output for Name and Version
|
|
16
|
+
const nameMatch = stdout.match(/Name:\s*(.+)/i);
|
|
17
|
+
const versionMatch = stdout.match(/Version:\s*(.+)/i);
|
|
18
|
+
|
|
19
|
+
if (nameMatch) {
|
|
20
|
+
const version = versionMatch ? versionMatch[1].trim() : null;
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
name: packageName,
|
|
24
|
+
installed: true,
|
|
25
|
+
version,
|
|
26
|
+
meetsMinimum: true, // Always true if installed
|
|
27
|
+
guidance: `guidance.install${packageName.charAt(0).toUpperCase() + packageName.slice(1)}`
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
// Package not installed or pip not available
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: packageName,
|
|
36
|
+
installed: false,
|
|
37
|
+
version: null,
|
|
38
|
+
meetsMinimum: false,
|
|
39
|
+
guidance: `guidance.install${packageName.charAt(0).toUpperCase() + packageName.slice(1)}`
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { detectPipPackage };
|