@deloskiyt/samcli 1.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.
Files changed (3) hide show
  1. package/README.md +107 -0
  2. package/package.json +39 -0
  3. package/samcli.js +258 -0
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # samcli
2
+
3
+ An interactive, zero-dependency, ultra-lightweight Node.js command-line utility to query the Samsung CheckFirm API.
4
+
5
+ ## Features
6
+
7
+ - **Interactive Mode**: Runs a step-by-step wizard to prompt you for the Model, CSC/Region, and API Key (with smart defaults).
8
+ - **Direct CLI Mode**: Pass parameters directly (`node samcli.js <model> <csc> [api_key]`) for instant results.
9
+ - **Zero Dependencies**: Pure Node.js! No need for `npm install` or massive `node_modules` folders.
10
+ - **Rich UI/UX**: Includes an ASCII art header, animated loader spinner, and colored box-drawing layout output.
11
+ - **Rate-Limited by default**: Free tier allows up to 2 queries per day per IP. You can bypass the limit by providing a valid API key.
12
+
13
+ ---
14
+
15
+ ## Special Acknowledgments
16
+
17
+ ### 🌟 Special Thanks to **killshot**!
18
+ This tool and its UI improvements were created with key contributions and inspiration from **killshot**. Thank you for helping shape the user experience!
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ Ensure you have [Node.js](https://nodejs.org/) installed (v12.0.0 or higher).
25
+
26
+ ### Method 1: Global CLI Install (Recommended)
27
+
28
+ You can install this tool globally directly from the NPM Registry:
29
+
30
+ ```bash
31
+ # Using npm
32
+ npm install -g @deloskiyt/samcli
33
+
34
+ # Using yarn
35
+ yarn global add @deloskiyt/samcli
36
+ ```
37
+
38
+ Or install it directly from GitHub:
39
+
40
+ ```bash
41
+ # Using npm
42
+ npm install -g ktopytal/samcli
43
+
44
+ # Using yarn
45
+ yarn global add ktopytal/samcli
46
+ ```
47
+
48
+ Once installed, you can run the tool from anywhere in your terminal using the `samcli` command!
49
+
50
+
51
+ ### Method 2: Local Run (No Install)
52
+
53
+ 1. Clone this repository:
54
+ ```bash
55
+ git clone https://github.com/ktopytal/samcli.git
56
+ cd samcli
57
+ ```
58
+ 2. Run the script directly:
59
+ ```bash
60
+ node samcli.js
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Usage Examples
66
+
67
+ ### 1. Interactive Mode (Wizard)
68
+ Simply execute the command without arguments:
69
+ ```bash
70
+ # If installed globally
71
+ samcli
72
+
73
+ # If running locally
74
+ node samcli.js
75
+ ```
76
+
77
+ Follow the prompts in your terminal:
78
+ - Enter Model (e.g. `SM-S928B`)
79
+ - Enter Region/CSC (e.g. `EUX`)
80
+ - Enter API Key (optional, press Enter to use the free tier limit of 2 requests per day)
81
+
82
+ ### 2. Direct CLI Arguments
83
+ Check a device instantly:
84
+ ```bash
85
+ # If installed globally
86
+ samcli SM-S928B EUX
87
+
88
+ # If running locally
89
+ node samcli.js SM-S928B EUX
90
+ ```
91
+
92
+ Query with a private API key to bypass limits:
93
+ ```bash
94
+ # If installed globally
95
+ samcli SM-S928B EUX your_api_key_here
96
+
97
+ # If running locally
98
+ node samcli.js SM-S928B EUX your_api_key_here
99
+ ```
100
+
101
+
102
+ ---
103
+
104
+ ## License
105
+
106
+ This project is licensed under the MIT License.
107
+
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@deloskiyt/samcli",
3
+
4
+
5
+ "version": "1.1.0",
6
+ "description": "Interactive zero-dependency CLI client for Samsung CheckFirm API",
7
+ "main": "samcli.js",
8
+ "preferGlobal": true,
9
+ "bin": {
10
+ "samcli": "./samcli.js"
11
+ },
12
+ "scripts": {
13
+ "start": "node samcli.js"
14
+ },
15
+
16
+ "keywords": [
17
+ "samsung",
18
+ "checkfirm",
19
+ "firmware",
20
+ "cli",
21
+ "node",
22
+ "interactive",
23
+ "samcli"
24
+ ],
25
+ "author": "shaza",
26
+ "contributors": [
27
+ "killshot"
28
+ ],
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/ktopytal/samcli.git"
33
+ },
34
+
35
+
36
+ "engines": {
37
+ "node": ">=12.0.0"
38
+ }
39
+ }
package/samcli.js ADDED
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ const readline = require('readline');
3
+ const https = require('https');
4
+
5
+
6
+ const rl = readline.createInterface({
7
+ input: process.stdin,
8
+ output: process.stdout
9
+ });
10
+
11
+ const askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
12
+
13
+ function makeRequest(url) {
14
+ return new Promise((resolve, reject) => {
15
+ https.get(url, (res) => {
16
+ let data = '';
17
+ res.on('data', (chunk) => { data += chunk; });
18
+ res.on('end', () => {
19
+ try {
20
+ const parsed = JSON.parse(data);
21
+ if (res.statusCode >= 200 && res.statusCode < 300) {
22
+ resolve(parsed);
23
+ } else {
24
+ reject(new Error(parsed.error || parsed.detail || `Server error (${res.statusCode})`));
25
+ }
26
+ } catch (e) {
27
+ if (res.statusCode === 429) {
28
+ reject(new Error('Rate limit exceeded. Free limit is 2 queries per day per IP.'));
29
+ } else {
30
+ reject(new Error(`HTTP Error ${res.statusCode} (Failed to parse response)`));
31
+ }
32
+ }
33
+ });
34
+ }).on('error', (err) => {
35
+ reject(err);
36
+ });
37
+ });
38
+ }
39
+
40
+ function drawBox(title, rows) {
41
+ const width = 85;
42
+ const horizontalLine = '─'.repeat(width - 2);
43
+
44
+ console.log(`\x1b[36m┌${horizontalLine}┐\x1b[0m`);
45
+ // Center title
46
+ const paddingLeft = Math.floor((width - 2 - title.length) / 2);
47
+ const paddingRight = width - 2 - title.length - paddingLeft;
48
+ console.log(`\x1b[36m│\x1b[0m${' '.repeat(paddingLeft)}\x1b[1m\x1b[33m${title}\x1b[0m${' '.repeat(paddingRight)}\x1b[36m│\x1b[0m`);
49
+ console.log(`\x1b[36m├${horizontalLine}┤\x1b[0m`);
50
+
51
+ rows.forEach(row => {
52
+ if (row === 'sep') {
53
+ console.log(`\x1b[36m├${horizontalLine}┤\x1b[0m`);
54
+ return;
55
+ }
56
+ const label = row.label;
57
+ const val = String(row.value || '');
58
+ const color = row.color || '\x1b[37m'; // Default white
59
+
60
+ // Format line: "│ Label: [Value] │"
61
+ const content = ` \x1b[1m${label.padEnd(26)}\x1b[0m ${color}${val}\x1b[0m`;
62
+ // Calculate length without ANSI escape codes for padding
63
+ const cleanContent = ` ${label.padEnd(26)} ${val}`;
64
+ const trailingSpaces = (width - 2) - cleanContent.length;
65
+
66
+ if (trailingSpaces >= 0) {
67
+ console.log(`\x1b[36m│\x1b[0m${content}${' '.repeat(trailingSpaces)}\x1b[36m│\x1b[0m`);
68
+ } else {
69
+ // Truncate safely if too long
70
+ const maxValLen = (width - 2) - 30; // 30 is label + padding
71
+ const truncatedVal = val.substring(0, maxValLen - 3) + '...';
72
+ const truncContent = ` \x1b[1m${label.padEnd(26)}\x1b[0m ${color}${truncatedVal}\x1b[0m`;
73
+ const cleanTruncContent = ` ${label.padEnd(26)} ${truncatedVal}`;
74
+ const truncTrailing = (width - 2) - cleanTruncContent.length;
75
+ console.log(`\x1b[36m│\x1b[0m${truncContent}${' '.repeat(truncTrailing)}\x1b[36m│\x1b[0m`);
76
+ }
77
+ });
78
+ console.log(`\x1b[36m└${horizontalLine}┘\x1b[0m`);
79
+ }
80
+
81
+ const CURRENT_VERSION = '1.1.0';
82
+
83
+
84
+
85
+ function fetchRemoteVersion() {
86
+ return new Promise((resolve) => {
87
+ const req = https.get('https://raw.githubusercontent.com/ktopytal/samcli/main/package.json', { timeout: 3000 }, (res) => {
88
+ let data = '';
89
+ res.on('data', (chunk) => { data += chunk; });
90
+ res.on('end', () => {
91
+ try {
92
+ const pkg = JSON.parse(data);
93
+ resolve(pkg.version || null);
94
+ } catch (e) {
95
+ resolve(null);
96
+ }
97
+ });
98
+ });
99
+ req.on('error', () => resolve(null));
100
+ req.on('timeout', () => { req.destroy(); resolve(null); });
101
+ });
102
+ }
103
+
104
+ function isNewerVersion(local, remote) {
105
+ const localParts = local.split('.').map(Number);
106
+ const remoteParts = remote.split('.').map(Number);
107
+ for (let i = 0; i < 3; i++) {
108
+ if (remoteParts[i] > localParts[i]) return true;
109
+ if (remoteParts[i] < localParts[i]) return false;
110
+ }
111
+ return false;
112
+ }
113
+
114
+ function runUpdate() {
115
+ const { execSync } = require('child_process');
116
+ console.log('\n \x1b[33mUpdating SamCLI to the latest version...\x1b[0m');
117
+ try {
118
+ execSync('npm install -g ktopytal/samcli', { stdio: 'inherit' });
119
+ console.log(' \x1b[32m✔ Successfully updated! Please restart the tool.\x1b[0m\n');
120
+ process.exit(0);
121
+ } catch (e) {
122
+ console.log(' \x1b[31m✘ Update failed. Please run manually: npm install -g ktopytal/samcli\x1b[0m\n');
123
+ }
124
+ }
125
+
126
+ function printHeader() {
127
+ console.clear();
128
+ console.log(`\x1b[36m
129
+ ____ _ _ _____ _cm
130
+ / ___| |__ ___ ___| | __| ___(_)_ __ _ __ ___
131
+ | | | '_ \\ / _ \\/ __| |/ /| |_ | | '__| '_ \` _ \\
132
+ | |___| | | | __/ (__| < | _| | | | | | | | | |
133
+ \\____|_| |_|\\___|\\___|_|\\_\\|_| |_|_| |_| |_| |_|
134
+ \x1b[0m`);
135
+ console.log('\x1b[35m =======================================================\x1b[0m');
136
+ console.log('\x1b[1m SamCLI v1.1.0 • Public Edition\x1b[0m');
137
+
138
+ console.log('\x1b[90m Special thanks to \x1b[33mkillshot\x1b[90m for inspiration & contributions!\x1b[0m');
139
+ console.log('\x1b[35m =======================================================\x1b[0m\n');
140
+ }
141
+
142
+ async function main() {
143
+ const remoteVersionPromise = fetchRemoteVersion();
144
+ const args = process.argv.slice(2);
145
+
146
+ // Support version check commands
147
+ if (args.length > 0) {
148
+ const firstArg = args[0].toLowerCase();
149
+ if (firstArg === '/version' || firstArg === '-v' || firstArg === '--version' || firstArg === 'version') {
150
+ rl.close();
151
+ console.log(`\x1b[36mSamCLI v${CURRENT_VERSION}\x1b[0m`);
152
+ console.log('\x1b[90mSpecial thanks to \x1b[33mkillshot\x1b[90m for inspiration & contributions!\x1b[0m');
153
+ process.exit(0);
154
+ }
155
+ }
156
+
157
+ let model = '';
158
+ let csc = '';
159
+ let key = '';
160
+
161
+ if (args.length > 0) {
162
+ // CLI Arguments Mode
163
+ model = args[0] || 'SM-S928B';
164
+ csc = args[1] || 'EUX';
165
+ key = args[2] || '';
166
+
167
+ // Intelligent parameter shift if key is passed as second param:
168
+ if (csc.length > 5) {
169
+ key = csc;
170
+ csc = 'EUX';
171
+ }
172
+
173
+ // Remove "=" if copied by mistake
174
+ if (csc.startsWith('=')) csc = csc.substring(1);
175
+ if (key.startsWith('=')) key = key.substring(1);
176
+
177
+ rl.close();
178
+ printHeader();
179
+ } else {
180
+ // Interactive Mode
181
+ printHeader();
182
+ console.log('\x1b[90m (Press Enter to accept default values)\x1b[0m\n');
183
+
184
+ model = await askQuestion(' \x1b[32m? Enter Samsung Model (default: SM-S928B): \x1b[0m');
185
+ model = model.trim() || 'SM-S928B';
186
+
187
+ csc = await askQuestion(' \x1b[32m? Enter Region/CSC (default: EUX): \x1b[0m');
188
+ csc = csc.trim() || 'EUX';
189
+
190
+ key = await askQuestion(' \x1b[32m? Enter API Key (optional, press Enter for free limit): \x1b[0m');
191
+ key = key.trim();
192
+
193
+ rl.close();
194
+ printHeader();
195
+ }
196
+
197
+ // Auto-update check
198
+ const remoteVersion = await remoteVersionPromise;
199
+ if (remoteVersion && isNewerVersion(CURRENT_VERSION, remoteVersion)) {
200
+ if (args.length > 0) {
201
+ console.log(` \x1b[33m📢 A new version of SamCLI is available: v${remoteVersion} (Current: v${CURRENT_VERSION})\x1b[0m`);
202
+ console.log(` \x1b[33m Run 'npm install -g ktopytal/samcli' to update.\x1b[0m\n`);
203
+ } else {
204
+ console.log(` \x1b[33m📢 A new version of SamCLI is available: v${remoteVersion} (Current: v${CURRENT_VERSION})\x1b[0m`);
205
+ const answer = await askQuestion(' \x1b[32m? Would you like to auto-update now? (Y/n): \x1b[0m');
206
+ if (answer.trim().toLowerCase() !== 'n') {
207
+ runUpdate();
208
+ }
209
+ printHeader();
210
+ }
211
+ }
212
+
213
+ console.log(' \x1b[90mPreparing API query...\x1b[0m');
214
+
215
+ // Build URL
216
+ const url = `https://api.checkfirm.pl/api/check/v2/${model}/${csc}?key=${key}`;
217
+
218
+ // Start Spinner
219
+ const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
220
+ let spinnerIndex = 0;
221
+ const spinnerInterval = setInterval(() => {
222
+ process.stdout.write(`\r \x1b[33m${spinnerFrames[spinnerIndex++ % spinnerFrames.length]} Loading... Querying server, please wait...\x1b[0m`);
223
+ }, 80);
224
+
225
+ try {
226
+ const data = await makeRequest(url);
227
+ clearInterval(spinnerInterval);
228
+ process.stdout.write('\r\x1b[K'); // Clear line
229
+
230
+ console.log(' \x1b[32m✔ Server responded successfully!\x1b[0m\n');
231
+
232
+ drawBox('SAMSUNG FIRMWARE DATA', [
233
+ { label: 'Model Name:', value: `${data.model || model} (${data.region || csc})`, color: '\x1b[37m' },
234
+ 'sep',
235
+ { label: 'Official Version:', value: data.latest_official || 'N/A', color: '\x1b[32m' },
236
+ { label: 'Internal Test Version:', value: data.latest_internal || 'N/A', color: '\x1b[36m' },
237
+ 'sep',
238
+ { label: 'Android / One UI Version:', value: `Android ${data.android_version || 'N/A'} / One UI ${data.oneui_version || 'N/A'}`, color: '\x1b[35m' },
239
+ { label: 'Security Patch Level:', value: data.patch_level || 'N/A', color: '\x1b[33m' }
240
+ ]);
241
+ console.log(`\n \x1b[90mQuery URL: ${url}\x1b[0m\n`);
242
+ } catch (err) {
243
+ clearInterval(spinnerInterval);
244
+ process.stdout.write('\r\x1b[K'); // Clear line
245
+
246
+ console.log(' \x1b[31m✘ Query failed!\x1b[0m\n');
247
+
248
+ drawBox('ERROR INFORMATION', [
249
+ { label: 'Target Model:', value: `${model} (${csc})`, color: '\x1b[37m' },
250
+ { label: 'Status:', value: 'Failed to retrieve data', color: '\x1b[31m' },
251
+ 'sep',
252
+ { label: 'Error Details:', value: err.message, color: '\x1b[31m' }
253
+ ]);
254
+ console.log(`\n \x1b[90mQuery URL: ${url}\x1b[0m\n`);
255
+ }
256
+ }
257
+
258
+ main();