@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 ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { main } = require('../src/index.js');
6
+
7
+ main().catch((error) => {
8
+ console.error('Fatal error:', error);
9
+ process.exit(1);
10
+ });
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 };