error-ux-cli 1.0.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/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Project
2
+
3
+ ## 🚀 Changelog
4
+
5
+ v1 - final test verdict
6
+ v1 - final
7
+ v1 - hi bye
package/index.js ADDED
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env node
2
+
3
+ const readline = require('readline');
4
+ const commands = require('./lib/commands');
5
+ const utils = require('./lib/utils');
6
+ const storage = require('./lib/storage');
7
+ const auth = require('./lib/auth');
8
+
9
+ async function main() {
10
+ utils.loadEnv();
11
+ const args = process.argv.slice(2);
12
+
13
+ const rl = readline.createInterface({
14
+ input: process.stdin,
15
+ output: process.stdout,
16
+ prompt: 'error-ux > '
17
+ });
18
+
19
+ try {
20
+ let config = await storage.loadConfig();
21
+
22
+ // 1. Onboarding
23
+ if (!config || !config.users || Object.keys(config.users).length === 0) {
24
+ config = await commands.initOnboarding(rl);
25
+ }
26
+
27
+ // 2. Multi-User Login
28
+ const usernames = Object.keys(config.users);
29
+ let targetUser = config.activeUser;
30
+
31
+ if (usernames.length > 1) {
32
+ console.log('\nProfiles: ' + usernames.join(', '));
33
+ const selected = await commands.askQuestion(rl, 'Username: ');
34
+ if (!config.users[selected]) {
35
+ console.error('error');
36
+ process.exit(1);
37
+ }
38
+ targetUser = selected;
39
+ }
40
+
41
+ let authenticated = false;
42
+ let attempts = 0;
43
+ const storedHash = config.users[targetUser].password_hash;
44
+
45
+ while (!authenticated && attempts < 3) {
46
+ const pwd = await auth.askPassword(rl, `Password (${targetUser}): `);
47
+ if (auth.verifyPassword(pwd, storedHash)) {
48
+ authenticated = true;
49
+ config.activeUser = targetUser;
50
+ await commands.ensureUserNewsApiKey(rl, config, config.users[targetUser], {forcePrompt: true});
51
+ await storage.saveConfig(config);
52
+ } else {
53
+ attempts++;
54
+ if (attempts >= 3) {
55
+ console.error('error');
56
+ process.exit(1);
57
+ }
58
+ console.log('Incorrect password.');
59
+ }
60
+ }
61
+
62
+ // 3. Execution
63
+ if (args.length > 0) {
64
+ try {
65
+ await processInput(rl, args.join(' '), true);
66
+ } catch (err) {
67
+ console.error('error');
68
+ }
69
+ rl.close();
70
+ } else {
71
+ try {
72
+ await commands.handleDashboard(rl);
73
+ } catch (err) {
74
+ console.error('error');
75
+ }
76
+ rl.prompt();
77
+
78
+ rl.on('line', async (line) => {
79
+ try {
80
+ await processInput(rl, line.trim(), false);
81
+ } catch (err) {
82
+ console.error('error');
83
+ }
84
+ rl.prompt();
85
+ }).on('close', () => {
86
+ console.log('\nExiting error-ux...');
87
+ process.exit(0);
88
+ });
89
+ }
90
+ } catch (err) {
91
+ console.error('error');
92
+ process.exit(1);
93
+ }
94
+ }
95
+
96
+ async function processInput(rl, input, isDirect) {
97
+ const trimmedInput = input.trim();
98
+ if (trimmedInput === '' || trimmedInput === 'help') {
99
+ await commands.handleMenu();
100
+ return;
101
+ }
102
+
103
+ const firstWord = trimmedInput.split(' ')[0].toLowerCase();
104
+ const restOfInput = trimmedInput.slice(firstWord.length).trim();
105
+
106
+ switch (firstWord) {
107
+ case 'user':
108
+ await commands.handleUser(rl, restOfInput);
109
+ break;
110
+ case 'export':
111
+ case 'export-data':
112
+ await commands.handleExport(rl, restOfInput);
113
+ break;
114
+ case 'import':
115
+ case 'import-data':
116
+ await commands.handleImport(rl, restOfInput);
117
+ break;
118
+ case 'link':
119
+ await commands.handleLink(rl);
120
+ break;
121
+ case 'links':
122
+ await commands.handleLinks();
123
+ break;
124
+ case 'unlink':
125
+ await commands.handleUnlink(rl, restOfInput);
126
+ break;
127
+ case 'push':
128
+ await commands.handlePush(rl, restOfInput);
129
+ break;
130
+ case 'news':
131
+ await commands.handleNews(rl, restOfInput);
132
+ break;
133
+ case 'todo':
134
+ await commands.handleTodo(rl, restOfInput);
135
+ break;
136
+ case 'repo':
137
+ await commands.handleRepo(rl, restOfInput);
138
+ break;
139
+ case 'uninstall':
140
+ await commands.handleUninstall(rl);
141
+ break;
142
+ case 'exit':
143
+ rl.close();
144
+ break;
145
+ default:
146
+ const found = await commands.handleShortcut(firstWord);
147
+ if (!found && trimmedInput !== '') {
148
+ console.error('error');
149
+ }
150
+ break;
151
+ }
152
+ }
153
+
154
+ main();
package/lib/auth.js ADDED
@@ -0,0 +1,52 @@
1
+ const crypto = require('crypto');
2
+
3
+ /**
4
+ * Hashes a password using scryptSync.
5
+ * Returns salt:hash format.
6
+ */
7
+ function hashPassword(password) {
8
+ const salt = crypto.randomBytes(16).toString('hex');
9
+ const hash = crypto.scryptSync(password, salt, 64).toString('hex');
10
+ return `${salt}:${hash}`;
11
+ }
12
+
13
+ /**
14
+ * Verifies a password against a salt:hash string.
15
+ */
16
+ function verifyPassword(password, storedValue) {
17
+ if (!storedValue || !storedValue.includes(':')) return false;
18
+ const [salt, hash] = storedValue.split(':');
19
+ if (!salt || !hash) return false;
20
+ const derivedHash = crypto.scryptSync(password, salt, 64).toString('hex');
21
+ return derivedHash === hash;
22
+ }
23
+
24
+ /**
25
+ * Prompts for password with input masking.
26
+ */
27
+ function askPassword(rl, query = 'Password: ') {
28
+ return new Promise((resolve) => {
29
+ // We override _writeToOutput on the internal readline output stream
30
+ const originalWrite = rl.output.write;
31
+ rl.output.write = (string) => {
32
+ // Only mask actual characters, not the prompt itself or newlines
33
+ if (string === query) return originalWrite.apply(rl.output, [string]);
34
+ if (string === '\n' || string === '\r\n') return originalWrite.apply(rl.output, [string]);
35
+
36
+ // Masking character
37
+ return originalWrite.apply(rl.output, ['*']);
38
+ };
39
+
40
+ rl.question(query, (password) => {
41
+ rl.output.write = originalWrite; // Restore original write
42
+ console.log(''); // New line after entry
43
+ resolve(password);
44
+ });
45
+ });
46
+ }
47
+
48
+ module.exports = {
49
+ hashPassword,
50
+ verifyPassword,
51
+ askPassword
52
+ };