@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.
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ /**
8
+ * Get Claude Code config.json path
9
+ * @returns {string} Full path to config.json
10
+ */
11
+ function getConfigPath() {
12
+ return path.join(os.homedir(), '.claude', 'config.json');
13
+ }
14
+
15
+ /**
16
+ * Read Claude Code configuration
17
+ * @returns {Object} Parsed config object (empty object if file missing or invalid)
18
+ */
19
+ function readClaudeConfig() {
20
+ const configPath = getConfigPath();
21
+
22
+ if (!fs.existsSync(configPath)) {
23
+ return {};
24
+ }
25
+
26
+ try {
27
+ const raw = fs.readFileSync(configPath, 'utf8');
28
+ return JSON.parse(raw);
29
+ } catch (error) {
30
+ // Parse error or read error - return empty config
31
+ return {};
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Write Claude Code configuration
37
+ * @param {Object} config - Configuration object to write
38
+ */
39
+ function writeClaudeConfig(config) {
40
+ const configPath = getConfigPath();
41
+
42
+ // Ensure directory exists
43
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
44
+
45
+ // Write formatted JSON
46
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
47
+ }
48
+
49
+ /**
50
+ * Register work-skills as a marketplace source in Claude Code config
51
+ * @returns {{success: boolean, path: string, error?: string}}
52
+ */
53
+ function registerMarketplaceSource() {
54
+ try {
55
+ const config = readClaudeConfig();
56
+
57
+ // Ensure marketplaceSources object exists
58
+ config.marketplaceSources = config.marketplaceSources || {};
59
+
60
+ // Add work-skills entry
61
+ config.marketplaceSources['work-skills'] = {
62
+ type: 'github',
63
+ url: 'https://github.com/allanpk716/work-skills',
64
+ branch: 'main'
65
+ };
66
+
67
+ writeClaudeConfig(config);
68
+
69
+ return {
70
+ success: true,
71
+ path: getConfigPath()
72
+ };
73
+ } catch (error) {
74
+ return {
75
+ success: false,
76
+ path: getConfigPath(),
77
+ error: error.message
78
+ };
79
+ }
80
+ }
81
+
82
+ module.exports = {
83
+ getConfigPath,
84
+ readClaudeConfig,
85
+ writeClaudeConfig,
86
+ registerMarketplaceSource
87
+ };
@@ -0,0 +1,157 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const { MultiSelect } = require('enquirer');
5
+ const { t } = require('../i18n/index.js');
6
+ const { registerMarketplaceSource } = require('./config-manager.js');
7
+ const { fetchMarketplaceJson, parsePluginList } = require('./plugin-discovery.js');
8
+ const { isPluginInstalled, installPlugins } = require('./plugin-installer.js');
9
+
10
+ /**
11
+ * Display plugin list as a table
12
+ * @param {Array} plugins - List of plugins to display
13
+ */
14
+ function displayPluginTable(plugins) {
15
+ console.log('\n' + chalk.bold(t('marketplace.available')));
16
+ console.log();
17
+
18
+ // Simple table format (no external table library)
19
+ const separator = chalk.gray('|----------------------|---------|------------------------------------------|');
20
+ const header = chalk.gray('| Name | Version | Description |');
21
+ console.log(separator);
22
+ console.log(header);
23
+ console.log(separator);
24
+
25
+ plugins.forEach(plugin => {
26
+ const name = plugin.name.padEnd(20).substring(0, 20);
27
+ const version = plugin.version.padEnd(7).substring(0, 7);
28
+ const desc = (plugin.description || '').padEnd(40).substring(0, 40);
29
+ const installed = isPluginInstalled(plugin.name);
30
+ const status = installed ? chalk.green(' [installed]') : '';
31
+ console.log(`| ${chalk.cyan(name)} | ${chalk.yellow(version)} | ${desc}|${status}`);
32
+ });
33
+
34
+ console.log(separator);
35
+ }
36
+
37
+ /**
38
+ * Display installation summary
39
+ * @param {Object} result - {installed, skipped, failed}
40
+ */
41
+ function displayInstallSummary(result) {
42
+ console.log('\n' + chalk.bold(t('marketplace.summary')));
43
+ console.log(chalk.gray('─'.repeat(60)));
44
+
45
+ if (result.installed.length > 0) {
46
+ console.log(chalk.green('✓ ' + t('marketplace.summary_installed', { count: result.installed.length })));
47
+ result.installed.forEach(p => console.log(chalk.gray(` - ${p.name} v${p.version}`)));
48
+ }
49
+
50
+ if (result.skipped.length > 0) {
51
+ console.log(chalk.yellow('⊘ ' + t('marketplace.summary_skipped', { count: result.skipped.length })));
52
+ result.skipped.forEach(p => console.log(chalk.gray(` - ${p.name}`)));
53
+ }
54
+
55
+ if (result.failed.length > 0) {
56
+ console.log(chalk.red('✗ ' + t('marketplace.summary_failed', { count: result.failed.length })));
57
+ result.failed.forEach(p => console.log(chalk.gray(` - ${p.name}: ${p.error}`)));
58
+ }
59
+
60
+ console.log(chalk.gray('─'.repeat(60)));
61
+
62
+ if (result.installed.length > 0) {
63
+ console.log(chalk.green('\n' + t('marketplace.complete')));
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Main marketplace integration function
69
+ * @returns {Promise<{success: boolean, installed: number, skipped: number, failed: number}>}
70
+ */
71
+ async function runMarketplaceIntegration() {
72
+ console.log(chalk.bold.blue('\n=== ' + t('marketplace.title') + ' ===\n'));
73
+
74
+ try {
75
+ // Step 1: Register marketplace source
76
+ console.log(chalk.gray(t('marketplace.registering')));
77
+ const registerResult = registerMarketplaceSource();
78
+
79
+ if (!registerResult.success) {
80
+ console.log(chalk.red('✗ Failed to register marketplace source'));
81
+ return { success: false, installed: 0, skipped: 0, failed: 0, error: registerResult.error };
82
+ }
83
+
84
+ console.log(chalk.green('✓ ' + t('marketplace.registered')));
85
+
86
+ // Step 2: Fetch plugin list
87
+ console.log(chalk.gray('\n' + t('marketplace.fetching')));
88
+ const marketplaceData = await fetchMarketplaceJson();
89
+ const plugins = parsePluginList(marketplaceData);
90
+
91
+ if (plugins.length === 0) {
92
+ console.log(chalk.yellow('No plugins available.'));
93
+ return { success: true, installed: 0, skipped: 0, failed: 0 };
94
+ }
95
+
96
+ // Step 3: Display plugins and prompt for selection
97
+ displayPluginTable(plugins);
98
+
99
+ const multiselect = new MultiSelect({
100
+ name: 'plugins',
101
+ message: t('marketplace.select_plugins'),
102
+ choices: plugins.map(p => ({
103
+ name: p.name,
104
+ message: `${p.name} (${p.version}) - ${(p.description || '').substring(0, 40)}...`,
105
+ disabled: isPluginInstalled(p.name)
106
+ })),
107
+ initial: []
108
+ });
109
+
110
+ const selectedNames = await multiselect.run();
111
+
112
+ if (selectedNames.length === 0) {
113
+ console.log(chalk.yellow('\n' + t('marketplace.none_selected')));
114
+ const alreadyInstalledCount = plugins.filter(p => isPluginInstalled(p.name)).length;
115
+ return { success: true, installed: 0, skipped: alreadyInstalledCount, failed: 0 };
116
+ }
117
+
118
+ // Step 4: Install selected plugins
119
+ const selectedPlugins = plugins.filter(p => selectedNames.includes(p.name));
120
+
121
+ const result = await installPlugins(selectedPlugins, {
122
+ onProgress: (plugin, status) => {
123
+ if (status === 'installing') {
124
+ console.log(chalk.gray(` ${t('marketplace.installing', { name: plugin.name })}`));
125
+ } else if (status === 'installed') {
126
+ console.log(chalk.green(` ✓ ${t('marketplace.install_success', { name: plugin.name })}`));
127
+ }
128
+ }
129
+ });
130
+
131
+ // Step 5: Display summary
132
+ displayInstallSummary(result);
133
+
134
+ return {
135
+ success: result.failed.length === 0,
136
+ installed: result.installed.length,
137
+ skipped: result.skipped.length,
138
+ failed: result.failed.length
139
+ };
140
+
141
+ } catch (error) {
142
+ // Handle specific errors
143
+ if (error.message.includes('Network') || error.message.includes('timeout')) {
144
+ console.log(chalk.red('\n✗ ' + t('marketplace.error.network')));
145
+ } else {
146
+ console.log(chalk.red('\n✗ ' + t('marketplace.error.unknown')));
147
+ console.log(chalk.gray(' ' + error.message));
148
+ }
149
+ return { success: false, installed: 0, skipped: 0, failed: 0, error: error.message };
150
+ }
151
+ }
152
+
153
+ module.exports = {
154
+ runMarketplaceIntegration,
155
+ displayPluginTable,
156
+ displayInstallSummary
157
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+
5
+ /**
6
+ * GitHub raw content URL for marketplace.json
7
+ */
8
+ const MARKETPLACE_URL = 'https://raw.githubusercontent.com/allanpk716/work-skills/main/.claude-plugin/marketplace.json';
9
+
10
+ /**
11
+ * Fetch marketplace.json from GitHub
12
+ * @param {number} timeout - Request timeout in milliseconds (default: 10000)
13
+ * @returns {Promise<Object>} Parsed marketplace.json object
14
+ */
15
+ function fetchMarketplaceJson(timeout = 10000) {
16
+ return new Promise((resolve, reject) => {
17
+ const req = https.get(MARKETPLACE_URL, { timeout }, (res) => {
18
+ let data = '';
19
+
20
+ res.on('data', (chunk) => {
21
+ data += chunk;
22
+ });
23
+
24
+ res.on('end', () => {
25
+ try {
26
+ const parsed = JSON.parse(data);
27
+ resolve(parsed);
28
+ } catch (err) {
29
+ reject(new Error('Failed to parse marketplace.json: ' + err.message));
30
+ }
31
+ });
32
+ });
33
+
34
+ req.on('error', (err) => {
35
+ reject(new Error('Network error: ' + err.message));
36
+ });
37
+
38
+ req.on('timeout', () => {
39
+ req.destroy();
40
+ reject(new Error('Request timeout'));
41
+ });
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Parse plugin list from marketplace data
47
+ * @param {Object} marketplaceData - Raw marketplace.json data
48
+ * @returns {Array<{name, description, version, source, category}>}
49
+ */
50
+ function parsePluginList(marketplaceData) {
51
+ if (!marketplaceData.plugins || !Array.isArray(marketplaceData.plugins)) {
52
+ return [];
53
+ }
54
+
55
+ return marketplaceData.plugins.map(p => ({
56
+ name: p.name,
57
+ description: p.description || '',
58
+ version: p.version || '0.0.0',
59
+ source: p.source || '',
60
+ category: p.category || 'general'
61
+ }));
62
+ }
63
+
64
+ module.exports = {
65
+ MARKETPLACE_URL,
66
+ fetchMarketplaceJson,
67
+ parsePluginList
68
+ };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const execa = require('execa');
7
+
8
+ /**
9
+ * GitHub repository URL for work-skills
10
+ */
11
+ const REPO_URL = 'https://github.com/allanpk716/work-skills.git';
12
+
13
+ /**
14
+ * Get Claude Code skills directory path
15
+ * @returns {string} Path to ~/.claude/skills
16
+ */
17
+ function getSkillsDir() {
18
+ return path.join(os.homedir(), '.claude', 'skills');
19
+ }
20
+
21
+ /**
22
+ * Check if a plugin is already installed
23
+ * @param {string} pluginName - Name of the plugin
24
+ * @returns {boolean} True if SKILL.md exists in plugin directory
25
+ */
26
+ function isPluginInstalled(pluginName) {
27
+ const skillPath = path.join(getSkillsDir(), pluginName, 'SKILL.md');
28
+ return fs.existsSync(skillPath);
29
+ }
30
+
31
+ /**
32
+ * Install a single plugin
33
+ * @param {Object} plugin - Plugin object {name, version, source}
34
+ * @param {Object} options - Options {tempDir, onProgress}
35
+ * @returns {Promise<{success: boolean, path?: string, error?: string}>}
36
+ */
37
+ async function installPlugin(plugin, options = {}) {
38
+ let tempDir = null;
39
+
40
+ try {
41
+ // Create temporary directory for cloning
42
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'work-skills-'));
43
+
44
+ // Notify progress
45
+ if (options.onProgress) {
46
+ options.onProgress(plugin, 'cloning');
47
+ }
48
+
49
+ // Clone repository with depth 1 (shallow clone)
50
+ await execa('git', ['clone', '--depth', '1', REPO_URL, tempDir]);
51
+
52
+ if (options.onProgress) {
53
+ options.onProgress(plugin, 'copying');
54
+ }
55
+
56
+ // Source and target paths
57
+ const sourcePath = path.join(tempDir, plugin.source.replace('./', ''));
58
+ const targetPath = path.join(getSkillsDir(), plugin.name);
59
+
60
+ // Ensure target directory exists
61
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
62
+
63
+ // Copy plugin directory to skills directory
64
+ fs.cpSync(sourcePath, targetPath, { recursive: true });
65
+
66
+ // Clean up temp directory
67
+ fs.rmSync(tempDir, { recursive: true, force: true });
68
+
69
+ if (options.onProgress) {
70
+ options.onProgress(plugin, 'installed');
71
+ }
72
+
73
+ return {
74
+ success: true,
75
+ path: targetPath
76
+ };
77
+ } catch (error) {
78
+ // Clean up on error
79
+ if (tempDir && fs.existsSync(tempDir)) {
80
+ try {
81
+ fs.rmSync(tempDir, { recursive: true, force: true });
82
+ } catch (cleanupError) {
83
+ // Ignore cleanup errors
84
+ }
85
+ }
86
+
87
+ return {
88
+ success: false,
89
+ error: error.message || 'Unknown error'
90
+ };
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Install multiple plugins
96
+ * @param {Array} plugins - Array of plugin objects {name, version, source}
97
+ * @param {Object} options - Options {onProgress}
98
+ * @returns {Promise<{installed: Array, skipped: Array, failed: Array}>}
99
+ */
100
+ async function installPlugins(plugins, options = {}) {
101
+ const result = {
102
+ installed: [],
103
+ skipped: [],
104
+ failed: []
105
+ };
106
+
107
+ for (const plugin of plugins) {
108
+ // Check if already installed
109
+ if (isPluginInstalled(plugin.name)) {
110
+ result.skipped.push({
111
+ name: plugin.name,
112
+ version: plugin.version
113
+ });
114
+ continue;
115
+ }
116
+
117
+ // Install plugin
118
+ const installResult = await installPlugin(plugin, options);
119
+
120
+ if (installResult.success) {
121
+ result.installed.push({
122
+ name: plugin.name,
123
+ version: plugin.version,
124
+ path: installResult.path
125
+ });
126
+ } else {
127
+ result.failed.push({
128
+ name: plugin.name,
129
+ version: plugin.version,
130
+ error: installResult.error
131
+ });
132
+ }
133
+ }
134
+
135
+ return result;
136
+ }
137
+
138
+ module.exports = {
139
+ REPO_URL,
140
+ getSkillsDir,
141
+ isPluginInstalled,
142
+ installPlugin,
143
+ installPlugins
144
+ };
@@ -0,0 +1,34 @@
1
+ 'use strict';
2
+
3
+ const { t } = require('./i18n/index.js');
4
+
5
+ /**
6
+ * Check if current platform is Windows
7
+ * @returns {boolean}
8
+ */
9
+ function isWindows() {
10
+ return process.platform === 'win32';
11
+ }
12
+
13
+ /**
14
+ * Check platform and exit with error if not Windows
15
+ * This function checks if the current platform is Windows.
16
+ * If not Windows, it prints an error message and exits with code 1.
17
+ * If Windows, it returns true.
18
+ * @returns {boolean} - true if Windows, never returns false (exits instead)
19
+ */
20
+ function checkPlatform() {
21
+ if (!isWindows()) {
22
+ console.error('');
23
+ console.error(t('error.windowsOnly'));
24
+ console.error(t('error.currentPlatform') + ': ' + process.platform);
25
+ console.error('');
26
+ process.exit(1);
27
+ }
28
+ return true;
29
+ }
30
+
31
+ module.exports = {
32
+ isWindows,
33
+ checkPlatform
34
+ };
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+
5
+ /**
6
+ * Format verification results as a simple ASCII table
7
+ * @param {Array<{name: string, status: string, symbol: string, details: string}>} results
8
+ * @returns {string} Formatted table string
9
+ */
10
+ function formatVerificationTable(results) {
11
+ const separator = chalk.gray('|---------------------------|------------|--------------------------------------------------|');
12
+ const header = chalk.gray('| Check | Status | Details |');
13
+
14
+ const lines = [separator, header, separator];
15
+
16
+ results.forEach(result => {
17
+ let statusIcon;
18
+ if (result.status === 'PASS') {
19
+ statusIcon = chalk.green('✓ PASS');
20
+ } else if (result.status === 'FAIL') {
21
+ statusIcon = chalk.red('✗ FAIL');
22
+ } else {
23
+ statusIcon = chalk.gray('⊘ SKIP');
24
+ }
25
+
26
+ const name = result.name.padEnd(25).substring(0, 25);
27
+ const status = statusIcon.padEnd(10);
28
+ const details = (result.details || '').padEnd(48).substring(0, 48);
29
+
30
+ lines.push(`| ${name} | ${status} | ${details}|`);
31
+ });
32
+
33
+ lines.push(separator);
34
+
35
+ return lines.join('\n');
36
+ }
37
+
38
+ /**
39
+ * Calculate verification summary statistics
40
+ * @param {Array<{status: string}>} results
41
+ * @returns {{passed: number, total: number}}
42
+ */
43
+ function calculateSummary(results) {
44
+ const passed = results.filter(r => r.status === 'PASS').length;
45
+ const total = results.length;
46
+
47
+ return { passed, total };
48
+ }
49
+
50
+ /**
51
+ * Display common solutions for failed checks
52
+ */
53
+ function displayCommonSolutions() {
54
+ console.log(chalk.yellow('\nCommon solutions:'));
55
+ console.log(' - Install missing Python libraries: pip install requests');
56
+ console.log(' - Set environment variables: PUSHOVER_TOKEN, PUSHOVER_USER');
57
+ console.log(' - Check PowerShell execution policy');
58
+ }
59
+
60
+ module.exports = {
61
+ formatVerificationTable,
62
+ calculateSummary,
63
+ displayCommonSolutions
64
+ };
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const { t } = require('../i18n/index.js');
5
+ const { runPythonVerification } = require('./runner.js');
6
+ const { parseVerificationOutput } = require('./parser.js');
7
+ const { formatVerificationTable, calculateSummary, displayCommonSolutions } = require('./formatter.js');
8
+
9
+ /**
10
+ * Run installation verification and display results
11
+ * @returns {Promise<{success: boolean, passed: number, total: number}>}
12
+ */
13
+ async function runVerification() {
14
+ // Display verification title
15
+ console.log(chalk.bold.blue('\n=== ' + t('verification.title') + ' ===\n'));
16
+
17
+ // Step 1: Execute Python verification script
18
+ const execResult = await runPythonVerification();
19
+
20
+ // Step 2: Handle execution errors
21
+ if (!execResult.success) {
22
+ if (execResult.error === 'python_not_found') {
23
+ console.error(chalk.red(t('verification.error.pythonNotFound')));
24
+ console.error(chalk.gray('Install Python 3.8+ from https://www.python.org/'));
25
+ return { success: false, passed: 0, total: 0 };
26
+ }
27
+
28
+ if (execResult.error === 'script_not_found') {
29
+ console.error(chalk.red(t('verification.error.scriptNotFound')));
30
+ return { success: false, passed: 0, total: 0 };
31
+ }
32
+
33
+ if (execResult.error === 'timeout') {
34
+ console.error(chalk.red(t('verification.error.timeout')));
35
+ return { success: false, passed: 0, total: 0 };
36
+ }
37
+
38
+ // Generic execution failure
39
+ console.error(chalk.red(t('verification.error.executionFailed')));
40
+ if (execResult.stderr) {
41
+ console.error(chalk.gray(execResult.stderr));
42
+ }
43
+ return { success: false, passed: 0, total: 0 };
44
+ }
45
+
46
+ // Step 3: Parse output
47
+ const results = parseVerificationOutput(execResult.stdout);
48
+
49
+ // Step 4: Format and display table
50
+ const table = formatVerificationTable(results);
51
+ console.log(table);
52
+
53
+ // Step 5: Calculate and display summary
54
+ const { passed, total } = calculateSummary(results);
55
+ console.log('\n' + t('verification.summary', { passed, total }));
56
+
57
+ // Step 6: Display common solutions if any checks failed
58
+ if (passed < total) {
59
+ displayCommonSolutions();
60
+ }
61
+
62
+ // Step 7: Display rerun command
63
+ console.log(chalk.gray('\n' + t('verification.rerunCommand')));
64
+
65
+ // Step 8: Display next steps guidance
66
+ console.log(chalk.cyan('\n=== ' + t('verification.nextSteps') + ' ==='));
67
+ console.log(chalk.white(t('verification.nextStep1')));
68
+ console.log(chalk.white(t('verification.nextStep2')));
69
+ console.log(chalk.white(t('verification.nextStep3')));
70
+ console.log(chalk.gray(t('verification.nextStep4')));
71
+
72
+ // Step 9: Return result (success requires at least 5/7 checks to pass)
73
+ const success = passed >= 5;
74
+ return { success, passed, total };
75
+ }
76
+
77
+ module.exports = {
78
+ runVerification
79
+ };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Parse Python verification script output into structured results
5
+ * @param {string} stdout - Raw output from Python script
6
+ * @returns {Array<{name: string, status: string, symbol: string, details: string}>}
7
+ */
8
+ function parseVerificationOutput(stdout) {
9
+ if (!stdout || stdout.trim() === '') {
10
+ return [];
11
+ }
12
+
13
+ const lines = stdout.split('\n');
14
+ const results = [];
15
+
16
+ // Pattern: " [OK] Python version: PASS"
17
+ // Pattern: " [X] Environment Variables: FAIL"
18
+ // Pattern: " [OK] Pushover API: SKIP"
19
+ const pattern = /^\s*\[(OK|X)\]\s*(.+?):\s*(PASS|FAIL|SKIP)/;
20
+
21
+ for (let i = 0; i < lines.length; i++) {
22
+ const line = lines[i];
23
+ const match = line.match(pattern);
24
+
25
+ if (match) {
26
+ const [, symbol, name, status] = match;
27
+
28
+ // Check if next line is a detail line (indented, not a result line)
29
+ let details = '';
30
+ if (i + 1 < lines.length) {
31
+ const nextLine = lines[i + 1].trim();
32
+ // Detail lines start with spaces but don't match the result pattern
33
+ if (nextLine && !nextLine.match(/^\s*\[(OK|X)\]/)) {
34
+ details = nextLine;
35
+ i++; // Skip the detail line in next iteration
36
+ }
37
+ }
38
+
39
+ results.push({
40
+ name: name.trim(),
41
+ status, // 'PASS' | 'FAIL' | 'SKIP'
42
+ symbol, // 'OK' | 'X'
43
+ details
44
+ });
45
+ }
46
+ }
47
+
48
+ return results;
49
+ }
50
+
51
+ module.exports = {
52
+ parseVerificationOutput
53
+ };