auto-clock-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/clock.js ADDED
@@ -0,0 +1,1050 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const os = require('os');
7
+ const { execSync } = require('child_process');
8
+
9
+ // Store config in user's home directory
10
+ const CONFIG_DIR = path.join(os.homedir(), '.auto-clock');
11
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
12
+ const LOG_PATH = path.join(CONFIG_DIR, 'clock.log');
13
+ const API_BASE = 'https://api-hrms.techbodia.com/api';
14
+
15
+ // Ensure config directory exists
16
+ if (!fs.existsSync(CONFIG_DIR)) {
17
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
18
+ }
19
+
20
+ // Helper to prompt user input
21
+ function prompt(question) {
22
+ const rl = readline.createInterface({
23
+ input: process.stdin,
24
+ output: process.stdout,
25
+ });
26
+ return new Promise((resolve) => {
27
+ rl.question(question, (answer) => {
28
+ rl.close();
29
+ resolve(answer.trim());
30
+ });
31
+ });
32
+ }
33
+
34
+ // Load config
35
+ function loadConfig() {
36
+ if (!fs.existsSync(CONFIG_PATH)) {
37
+ return null;
38
+ }
39
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
40
+ }
41
+
42
+ // Save config
43
+ function saveConfig(config) {
44
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
45
+ }
46
+
47
+ // Log to file
48
+ function log(message) {
49
+ const timestamp = new Date().toISOString();
50
+ const logMessage = `[${timestamp}] ${message}\n`;
51
+ fs.appendFileSync(LOG_PATH, logMessage);
52
+ console.log(message);
53
+ }
54
+
55
+ // Login and get fresh JWT
56
+ async function login(email, password, silent = false) {
57
+ if (!silent) console.log(`Logging in as ${email}...`);
58
+
59
+ const response = await fetch(`${API_BASE}/Auth/Login`, {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify({
63
+ Email: email,
64
+ Password: password,
65
+ visitorId: 'auto-clock-cli',
66
+ requestId: Date.now().toString(),
67
+ }),
68
+ });
69
+
70
+ const data = await response.json();
71
+
72
+ if (data.ErrorCode !== 0) {
73
+ throw new Error(data.ErrorMessage || 'Login failed');
74
+ }
75
+
76
+ if (!silent) console.log(`Welcome, ${data.Result.Username}!`);
77
+ return { jwt: data.Result.Jwt, username: data.Result.Username };
78
+ }
79
+
80
+ // Do clock in/out (using curl because fetch has issues with this API)
81
+ function doClock(jwt, isClockIn, location = 'TechBodia') {
82
+ const body = JSON.stringify({
83
+ IsClockIn: isClockIn,
84
+ IsInCompany: true,
85
+ Location: location,
86
+ ClockInRemark: '',
87
+ ClockOutRemark: '',
88
+ TimeZone: '07:00',
89
+ });
90
+
91
+ const cmd = `curl -s -X POST '${API_BASE}/Member/DoClock' \
92
+ -H 'accept: text/plain' \
93
+ -H 'Authorization: Bearer Bearer ${jwt}' \
94
+ -H 'Content-Type: application/json-patch+json' \
95
+ -d '${body}'`;
96
+
97
+ const result = execSync(cmd, { encoding: 'utf-8' });
98
+ const data = JSON.parse(result);
99
+
100
+ // Return the result with status info
101
+ return {
102
+ success: data.ErrorCode === 0,
103
+ alreadyDone: data.ErrorCode === 14 || data.ErrorCode === 15,
104
+ message: data.ErrorMessage,
105
+ errorCode: data.ErrorCode,
106
+ };
107
+ }
108
+
109
+ // Interactive setup wizard
110
+ async function setup() {
111
+ console.log('\n========================================');
112
+ console.log(' Auto Clock - Setup Wizard');
113
+ console.log('========================================\n');
114
+
115
+ const email = await prompt('Enter your email: ');
116
+ const password = await prompt('Enter your password: ');
117
+ const location = (await prompt('Enter location (default: TechBodia): ')) || 'TechBodia';
118
+ const clockInTime = (await prompt('Clock in time (HH:MM, default: 08:00): ')) || '08:00';
119
+ const clockOutTime = (await prompt('Clock out time (HH:MM, default: 17:00): ')) || '17:00';
120
+
121
+ console.log('\nVerifying credentials...');
122
+
123
+ try {
124
+ const { jwt, username } = await login(email, password);
125
+
126
+ const config = {
127
+ email,
128
+ password,
129
+ location,
130
+ clockInTime,
131
+ clockOutTime,
132
+ jwt,
133
+ username,
134
+ };
135
+
136
+ saveConfig(config);
137
+
138
+ console.log('\n----------------------------------------');
139
+ console.log('Setup complete!');
140
+ console.log('----------------------------------------');
141
+ console.log(`User: ${username}`);
142
+ console.log(`Location: ${location}`);
143
+ console.log(`Clock In: ${clockInTime}`);
144
+ console.log(`Clock Out: ${clockOutTime}`);
145
+ console.log(`Config saved to: ${CONFIG_PATH}`);
146
+ console.log('\nRun "auto-clock cron" to see scheduling instructions.');
147
+ console.log('----------------------------------------\n');
148
+ } catch (error) {
149
+ console.error('\nSetup failed:', error.message);
150
+ process.exit(1);
151
+ }
152
+ }
153
+
154
+ // Show cron instructions
155
+ function showCron() {
156
+ const config = loadConfig();
157
+ if (!config) {
158
+ console.error('Not configured. Run "auto-clock setup" first.');
159
+ process.exit(1);
160
+ }
161
+
162
+ const nodePath = process.execPath;
163
+ const scriptPath = path.join(__dirname, 'clock.js');
164
+
165
+ const [inHour, inMin] = config.clockInTime.split(':');
166
+ const [outHour, outMin] = config.clockOutTime.split(':');
167
+
168
+ console.log('\n========================================');
169
+ console.log(' Cron Setup Instructions');
170
+ console.log('========================================\n');
171
+
172
+ console.log('1. Open crontab editor:');
173
+ console.log(' crontab -e\n');
174
+
175
+ console.log('2. Add these lines:\n');
176
+ console.log('# Auto Clock - Clock In');
177
+ console.log(`${inMin} ${inHour} * * 1-5 ${nodePath} ${scriptPath} in >> ${LOG_PATH} 2>&1`);
178
+ console.log('');
179
+ console.log('# Auto Clock - Clock Out');
180
+ console.log(`${outMin} ${outHour} * * 1-5 ${nodePath} ${scriptPath} out >> ${LOG_PATH} 2>&1`);
181
+
182
+ console.log('\n3. Save and exit (:wq in vim)\n');
183
+
184
+ console.log('Your schedule:');
185
+ console.log(` Clock In: ${config.clockInTime} (Mon-Fri)`);
186
+ console.log(` Clock Out: ${config.clockOutTime} (Mon-Fri)\n`);
187
+
188
+ console.log('To verify: crontab -l');
189
+ console.log('To view logs: cat ' + LOG_PATH);
190
+ console.log('----------------------------------------\n');
191
+ }
192
+
193
+ // Set schedule times
194
+ async function setSchedule() {
195
+ const config = loadConfig();
196
+ if (!config) {
197
+ console.error('Not configured. Run "auto-clock setup" first.');
198
+ process.exit(1);
199
+ }
200
+
201
+ console.log(`\nCurrent schedule: In=${config.clockInTime}, Out=${config.clockOutTime}\n`);
202
+
203
+ const clockInTime = (await prompt(`New clock in time (HH:MM, current: ${config.clockInTime}): `)) || config.clockInTime;
204
+ const clockOutTime = (await prompt(`New clock out time (HH:MM, current: ${config.clockOutTime}): `)) || config.clockOutTime;
205
+
206
+ config.clockInTime = clockInTime;
207
+ config.clockOutTime = clockOutTime;
208
+ saveConfig(config);
209
+
210
+ console.log(`\nSchedule updated: In=${clockInTime}, Out=${clockOutTime}`);
211
+ console.log('Run "auto-clock cron" to update your cron jobs.\n');
212
+ }
213
+
214
+ // Show current status
215
+ function showStatus() {
216
+ const config = loadConfig();
217
+ if (!config) {
218
+ console.log('\nStatus: Not configured');
219
+ console.log('Run "auto-clock setup" to get started.\n');
220
+ return;
221
+ }
222
+
223
+ // Check if cron is enabled
224
+ let cronEnabled = false;
225
+ let cronType = '';
226
+ try {
227
+ const currentCron = execSync('crontab -l 2>/dev/null', { encoding: 'utf-8' });
228
+ if (currentCron.includes('clock.js smart')) {
229
+ cronEnabled = true;
230
+ cronType = 'Smart Mode';
231
+ } else if (currentCron.includes('clock.js in') || currentCron.includes('clock.js out')) {
232
+ cronEnabled = true;
233
+ cronType = 'Fixed Time';
234
+ }
235
+ } catch (e) {
236
+ // No crontab
237
+ }
238
+
239
+ console.log('\n========================================');
240
+ console.log(' Auto Clock Status');
241
+ console.log('========================================');
242
+ console.log(`User: ${config.username || 'Unknown'}`);
243
+ console.log(`Email: ${config.email}`);
244
+ console.log(`Location: ${config.location}`);
245
+ console.log(`Clock In: ${config.clockInTime}`);
246
+ console.log(`Clock Out: ${config.clockOutTime}`);
247
+ console.log('----------------------------------------');
248
+ if (cronEnabled) {
249
+ console.log(`Auto Clock: ENABLED (${cronType})`);
250
+ } else {
251
+ console.log(`Auto Clock: DISABLED`);
252
+ }
253
+ console.log('----------------------------------------');
254
+ console.log(`Config: ${CONFIG_PATH}`);
255
+ console.log(`Log: ${LOG_PATH}`);
256
+ console.log('========================================\n');
257
+ }
258
+
259
+ // Perform clock in/out
260
+ async function performClock(isClockIn) {
261
+ const config = loadConfig();
262
+ if (!config) {
263
+ console.error('Not configured. Run "auto-clock setup" first.');
264
+ process.exit(1);
265
+ }
266
+
267
+ const action = isClockIn ? 'Clock In' : 'Clock Out';
268
+
269
+ try {
270
+ // Try to login to get fresh JWT
271
+ let jwt = config.jwt;
272
+ try {
273
+ const result = await login(config.email, config.password, true);
274
+ if (result.jwt) {
275
+ jwt = result.jwt;
276
+ config.jwt = jwt;
277
+ saveConfig(config);
278
+ }
279
+ } catch (e) {
280
+ // Login failed, use existing token
281
+ }
282
+
283
+ if (!jwt) {
284
+ throw new Error('No JWT token. Run "auto-clock token" to set one.');
285
+ }
286
+
287
+ // Do clock
288
+ const result = doClock(jwt, isClockIn, config.location);
289
+ const user = config.username || config.email;
290
+
291
+ if (result.success) {
292
+ log(`${action} successful for ${user}`);
293
+ return true;
294
+ } else if (result.alreadyDone) {
295
+ log(`${action}: ${result.message} (${user})`);
296
+ return true; // Still considered success
297
+ } else {
298
+ throw new Error(result.message || 'Unknown error');
299
+ }
300
+ } catch (error) {
301
+ log(`${action} FAILED: ${error.message}`);
302
+ return false;
303
+ }
304
+ }
305
+
306
+ // Set JWT token manually
307
+ async function setToken() {
308
+ const config = loadConfig() || {};
309
+
310
+ console.log('\n========================================');
311
+ console.log(' Set JWT Token Manually');
312
+ console.log('========================================\n');
313
+ console.log('Get your JWT token from the HRMS web app:');
314
+ console.log('1. Login to HRMS website');
315
+ console.log('2. Open DevTools (F12) → Network tab');
316
+ console.log('3. Click any API request → Headers → Authorization');
317
+ console.log('4. Copy the token (after "Bearer ")\n');
318
+
319
+ const jwt = await prompt('Paste your JWT token: ');
320
+
321
+ if (!jwt) {
322
+ console.error('No token provided.');
323
+ process.exit(1);
324
+ }
325
+
326
+ config.jwt = jwt;
327
+
328
+ // If no email set, ask for basic info
329
+ if (!config.email) {
330
+ config.email = await prompt('Enter your email: ');
331
+ config.location = (await prompt('Enter location (default: TechBodia): ')) || 'TechBodia';
332
+ config.clockInTime = (await prompt('Clock in time (HH:MM, default: 08:00): ')) || '08:00';
333
+ config.clockOutTime = (await prompt('Clock out time (HH:MM, default: 17:00): ')) || '17:00';
334
+ }
335
+
336
+ saveConfig(config);
337
+ console.log('\nToken saved! Try "auto-clock in" to test.\n');
338
+ }
339
+
340
+ // Show help
341
+ function showHelp() {
342
+ console.log(`
343
+ Auto Clock CLI v1.0.0
344
+ =====================
345
+
346
+ Usage: auto-clock <command>
347
+
348
+ Commands:
349
+ setup Interactive setup wizard (first-time users)
350
+ token Manually set JWT token (if login doesn't return JWT)
351
+ in Clock in now
352
+ out Clock out now
353
+ smart Smart auto clock (checks if clock in/out is needed)
354
+ history [days] Show clock in/out history (default: 14 days)
355
+ reclock Request reclock for a single day
356
+ reclock-range Request reclock for multiple days (date range)
357
+ enable Enable auto clock (add cron jobs)
358
+ disable Disable auto clock (remove cron jobs)
359
+ schedule Change clock in/out times
360
+ cron Show cron setup instructions (manual)
361
+ status Show current configuration
362
+ logs Show recent logs
363
+ help Show this help message
364
+
365
+ Examples:
366
+ auto-clock setup # First-time setup
367
+ auto-clock token # Manually set JWT token
368
+ auto-clock in # Manual clock in
369
+ auto-clock out # Manual clock out
370
+ auto-clock smart # Smart clock (auto detects if in/out needed)
371
+ auto-clock history # Check clock history (14 days)
372
+ auto-clock history 7 # Check clock history (7 days)
373
+ auto-clock reclock # Request reclock for a single day
374
+ auto-clock reclock-range # Request reclock for multiple days
375
+ auto-clock enable # Enable smart auto clock (runs every 10 min)
376
+ auto-clock disable # Disable auto clock
377
+ auto-clock schedule # Change your times
378
+ auto-clock cron # Show cron commands (manual setup)
379
+
380
+ Config location: ${CONFIG_PATH}
381
+ `);
382
+ }
383
+
384
+ // Show logs
385
+ function showLogs() {
386
+ if (!fs.existsSync(LOG_PATH)) {
387
+ console.log('\nNo logs yet.\n');
388
+ return;
389
+ }
390
+
391
+ console.log('\n========================================');
392
+ console.log(' Recent Logs');
393
+ console.log('========================================\n');
394
+
395
+ const logs = fs.readFileSync(LOG_PATH, 'utf-8');
396
+ const lines = logs.trim().split('\n').slice(-20);
397
+ console.log(lines.join('\n'));
398
+ console.log('\n========================================\n');
399
+ }
400
+
401
+ // Format date with timezone offset +07:00
402
+ function formatDateWithTZ(date) {
403
+ const year = date.getFullYear();
404
+ const month = String(date.getMonth() + 1).padStart(2, '0');
405
+ const day = String(date.getDate()).padStart(2, '0');
406
+ const hours = String(date.getHours()).padStart(2, '0');
407
+ const minutes = String(date.getMinutes()).padStart(2, '0');
408
+ const seconds = String(date.getSeconds()).padStart(2, '0');
409
+ return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}+07:00`;
410
+ }
411
+
412
+ // Get time table history
413
+ function getHistory(jwt, days = 7) {
414
+ const endDate = new Date();
415
+ const startDate = new Date();
416
+ startDate.setDate(startDate.getDate() - days);
417
+
418
+ const body = JSON.stringify({
419
+ StartDate: formatDateWithTZ(startDate),
420
+ EndDate: formatDateWithTZ(endDate),
421
+ });
422
+
423
+ const cmd = `curl -s -X POST '${API_BASE}/Member/GetTimeTable' \
424
+ -H 'accept: text/plain' \
425
+ -H 'Authorization: Bearer Bearer ${jwt}' \
426
+ -H 'Content-Type: application/json-patch+json' \
427
+ -d '${body}'`;
428
+
429
+ const result = execSync(cmd, { encoding: 'utf-8' });
430
+ return JSON.parse(result);
431
+ }
432
+
433
+ // Show history command
434
+ async function showHistory(days = 14) {
435
+ const config = loadConfig();
436
+ if (!config) {
437
+ console.error('Not configured. Run "auto-clock setup" first.');
438
+ process.exit(1);
439
+ }
440
+
441
+ // Try to get fresh JWT
442
+ let jwt = config.jwt;
443
+ try {
444
+ const result = await login(config.email, config.password, true);
445
+ if (result.jwt) {
446
+ jwt = result.jwt;
447
+ config.jwt = jwt;
448
+ saveConfig(config);
449
+ }
450
+ } catch (e) {
451
+ // Use existing token
452
+ }
453
+
454
+ if (!jwt) {
455
+ console.error('No JWT token. Run "auto-clock token" to set one.');
456
+ process.exit(1);
457
+ }
458
+
459
+ const data = getHistory(jwt, days);
460
+
461
+ if (data.ErrorCode !== 0) {
462
+ console.error('Failed to get history:', data.ErrorMessage);
463
+ process.exit(1);
464
+ }
465
+
466
+ console.log('\n╔══════════════════════════════════════════════════════════════════════╗');
467
+ console.log(`║ Clock History (Last ${String(days).padStart(2)} days) ║`);
468
+ console.log('╠════════════╦══════════╦═══════════╦══════════╦═══════════════════════╣');
469
+ console.log('║ Date ║ Clock In ║ Clock Out ║ Status ║ Notes ║');
470
+ console.log('╠════════════╬══════════╬═══════════╬══════════╬═══════════════════════╣');
471
+
472
+ for (const day of data.Result) {
473
+ const date = day.WorkDate;
474
+ const clockIn = (day.ClockIn || '--:--').padStart(5).padEnd(5);
475
+ const clockOut = (day.ClockOut || '--:--').padStart(5).padEnd(5);
476
+
477
+ let status = '';
478
+ let notes = '';
479
+
480
+ // Determine status
481
+ if (day.LeaveInfo) {
482
+ status = 'Leave';
483
+ notes = 'On Leave';
484
+ } else if (!day.ClockIn && !day.ClockOut) {
485
+ status = 'Missing';
486
+ if (day.ReClockIn) {
487
+ notes = `ReClock: ${day.ReClockIn.Status === 0 ? 'Pending' : day.ReClockIn.Status === 1 ? 'Approved' : 'Rejected'}`;
488
+ }
489
+ } else if (!day.ClockOut) {
490
+ status = 'No Out';
491
+ if (day.ReClockOut) {
492
+ notes = `ReClockOut: ${day.ReClockOut.Status === 0 ? 'Pending' : 'Done'}`;
493
+ }
494
+ } else {
495
+ status = 'OK';
496
+ }
497
+
498
+ // Check for reclock info
499
+ if (day.ReClockIn && !notes) {
500
+ const reStatus = day.ReClockIn.Status === 0 ? 'Pending' : day.ReClockIn.Status === 1 ? 'Approved' : 'Rejected';
501
+ notes = `ReClockIn: ${reStatus}`;
502
+ }
503
+
504
+ console.log(`║ ${date} ║ ${clockIn} ║ ${clockOut} ║ ${status.padEnd(8)} ║ ${notes.padEnd(21)} ║`);
505
+ }
506
+
507
+ console.log('╚════════════╩══════════╩═══════════╩══════════╩═══════════════════════╝\n');
508
+ }
509
+
510
+ // Do reclock request
511
+ function doReClock(jwt, date, time, isClockIn, location, reason) {
512
+ const body = JSON.stringify({
513
+ Date: date,
514
+ Time: time,
515
+ Location: location,
516
+ Reason: reason,
517
+ IsClockIn: isClockIn,
518
+ });
519
+
520
+ const cmd = `curl -s -X POST '${API_BASE}/Member/DoReClock' \
521
+ -H 'accept: text/plain' \
522
+ -H 'Authorization: Bearer Bearer ${jwt}' \
523
+ -H 'Content-Type: application/json-patch+json' \
524
+ -d '${body}'`;
525
+
526
+ const result = execSync(cmd, { encoding: 'utf-8' });
527
+ return JSON.parse(result);
528
+ }
529
+
530
+ // Disable auto clock (remove cron jobs)
531
+ function disableAutoClock() {
532
+ console.log('\n========================================');
533
+ console.log(' Disable Auto Clock');
534
+ console.log('========================================\n');
535
+
536
+ try {
537
+ // Get current crontab
538
+ const currentCron = execSync('crontab -l 2>/dev/null || echo ""', { encoding: 'utf-8' });
539
+
540
+ // Filter out auto-clock entries
541
+ const lines = currentCron.split('\n');
542
+ const filteredLines = lines.filter(line => !line.includes('auto-clock') && !line.includes('clock.js'));
543
+
544
+ if (lines.length === filteredLines.length) {
545
+ console.log('No auto-clock cron jobs found.\n');
546
+ return;
547
+ }
548
+
549
+ // Write new crontab
550
+ const newCron = filteredLines.join('\n');
551
+ execSync(`echo "${newCron}" | crontab -`, { encoding: 'utf-8' });
552
+
553
+ console.log('Auto clock cron jobs have been removed!');
554
+ console.log('You can re-enable by running: auto-clock cron\n');
555
+ } catch (error) {
556
+ console.error('Failed to modify crontab:', error.message);
557
+ console.log('\nTo manually disable, run:');
558
+ console.log(' crontab -e');
559
+ console.log(' (remove lines containing "auto-clock" or "clock.js")\n');
560
+ }
561
+ }
562
+
563
+ // Enable auto clock (add cron jobs with smart mode)
564
+ function enableAutoClock() {
565
+ const config = loadConfig();
566
+ if (!config) {
567
+ console.error('Not configured. Run "auto-clock setup" first.');
568
+ process.exit(1);
569
+ }
570
+
571
+ const nodePath = process.execPath;
572
+ const scriptPath = path.join(__dirname, 'clock.js');
573
+ const [inHour] = config.clockInTime.split(':').map(Number);
574
+ const [outHour] = config.clockOutTime.split(':').map(Number);
575
+
576
+ // Fixed windows:
577
+ // - Clock In window: from clockInTime to 9:30 AM (hour 9)
578
+ // - Clock Out window: from clockOutTime to 6:00 PM (hour 18)
579
+ const inEndHour = 9; // 9:30 AM deadline
580
+ const outEndHour = 18; // 6:00 PM deadline
581
+
582
+ const clockInCron = `*/10 ${inHour}-${inEndHour} * * 1-5 ${nodePath} ${scriptPath} smart >> ${LOG_PATH} 2>&1`;
583
+ const clockOutCron = `*/10 ${outHour}-${outEndHour} * * 1-5 ${nodePath} ${scriptPath} smart >> ${LOG_PATH} 2>&1`;
584
+
585
+ console.log('\n========================================');
586
+ console.log(' Enable Smart Auto Clock');
587
+ console.log('========================================\n');
588
+
589
+ try {
590
+ // Get current crontab
591
+ let currentCron = '';
592
+ try {
593
+ currentCron = execSync('crontab -l 2>/dev/null', { encoding: 'utf-8' });
594
+ } catch (e) {
595
+ // No existing crontab
596
+ }
597
+
598
+ // Check if already exists
599
+ if (currentCron.includes('clock.js smart')) {
600
+ console.log('Smart auto clock is already enabled!\n');
601
+ console.log('Current settings:');
602
+ console.log(` Clock In Window: ${inHour}:00 - 9:30 AM (Mon-Fri)`);
603
+ console.log(` Clock Out Window: ${outHour}:00 - 6:00 PM (Mon-Fri)`);
604
+ console.log(` Check Interval: Every 10 minutes\n`);
605
+ return;
606
+ }
607
+
608
+ // Remove old entries first
609
+ const lines = currentCron.split('\n').filter(line =>
610
+ !line.includes('auto-clock') && !line.includes('clock.js') && line.trim()
611
+ );
612
+
613
+ // Add new smart entries for both windows
614
+ lines.push('# Auto Clock - Smart Mode (Clock In Window until 9:30 AM)');
615
+ lines.push(clockInCron);
616
+ lines.push('# Auto Clock - Smart Mode (Clock Out Window until 6 PM)');
617
+ lines.push(clockOutCron);
618
+
619
+ const newCron = lines.join('\n') + '\n';
620
+ execSync(`echo "${newCron}" | crontab -`, { encoding: 'utf-8' });
621
+
622
+ console.log('Smart auto clock has been enabled!');
623
+ console.log(`\nHow it works:`);
624
+ console.log(` - Clock In Window: ${inHour}:00 - 9:30 AM (every 10 min)`);
625
+ console.log(` - Clock Out Window: ${outHour}:00 - 6:00 PM (every 10 min)`);
626
+ console.log(` - Only runs Monday-Friday`);
627
+ console.log(`\nBenefit: Even if you open your laptop late, it will still clock you in!`);
628
+ console.log('\nTo disable: auto-clock disable\n');
629
+ } catch (error) {
630
+ console.error('Failed to modify crontab:', error.message);
631
+ console.log('\nTo manually enable, run: auto-clock cron\n');
632
+ }
633
+ }
634
+
635
+ // Smart auto clock - checks if clock in/out is needed
636
+ async function smartClock() {
637
+ const config = loadConfig();
638
+ if (!config) {
639
+ console.error('Not configured. Run "auto-clock setup" first.');
640
+ process.exit(1);
641
+ }
642
+
643
+ // Get fresh JWT
644
+ let jwt = config.jwt;
645
+ try {
646
+ const result = await login(config.email, config.password, true);
647
+ if (result.jwt) {
648
+ jwt = result.jwt;
649
+ config.jwt = jwt;
650
+ saveConfig(config);
651
+ }
652
+ } catch (e) {
653
+ // Use existing token
654
+ }
655
+
656
+ if (!jwt) {
657
+ log('Smart Clock: No JWT token');
658
+ return;
659
+ }
660
+
661
+ // Get today's status
662
+ const data = getHistory(jwt, 1);
663
+ if (data.ErrorCode !== 0) {
664
+ log(`Smart Clock: Failed to get status - ${data.ErrorMessage}`);
665
+ return;
666
+ }
667
+
668
+ const now = new Date();
669
+ const currentHour = now.getHours();
670
+ const currentMin = now.getMinutes();
671
+ const currentTime = currentHour * 60 + currentMin;
672
+ const dayOfWeek = now.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
673
+
674
+ // Only run Mon-Fri
675
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
676
+ return;
677
+ }
678
+
679
+ const [inHour, inMin] = config.clockInTime.split(':').map(Number);
680
+ const [outHour, outMin] = config.clockOutTime.split(':').map(Number);
681
+ const clockInTime = inHour * 60 + inMin;
682
+ const clockOutTime = outHour * 60 + outMin;
683
+
684
+ // Find today's record
685
+ const today = now.toISOString().split('T')[0];
686
+ const todayRecord = data.Result.find(d => d.WorkDate === today);
687
+
688
+ const hasClockIn = todayRecord?.ClockIn;
689
+ const hasClockOut = todayRecord?.ClockOut;
690
+
691
+ // Check if we should clock in
692
+ // Clock in window: from clockInTime to clockOutTime, if not already clocked in
693
+ if (!hasClockIn && currentTime >= clockInTime && currentTime < clockOutTime) {
694
+ log(`Smart Clock: Auto clocking in (opened late at ${String(currentHour).padStart(2, '0')}:${String(currentMin).padStart(2, '0')})`);
695
+ const result = doClock(jwt, true, config.location || 'TechBodia');
696
+ if (result.success) {
697
+ log(`Smart Clock: Clock In successful`);
698
+ } else if (result.alreadyDone) {
699
+ log(`Smart Clock: ${result.message}`);
700
+ } else {
701
+ log(`Smart Clock: Clock In failed - ${result.message}`);
702
+ }
703
+ return;
704
+ }
705
+
706
+ // Check if we should clock out
707
+ // Clock out window: after clockOutTime, if clocked in but not out
708
+ if (hasClockIn && !hasClockOut && currentTime >= clockOutTime) {
709
+ log(`Smart Clock: Auto clocking out (time: ${String(currentHour).padStart(2, '0')}:${String(currentMin).padStart(2, '0')})`);
710
+ const result = doClock(jwt, false, config.location || 'TechBodia');
711
+ if (result.success) {
712
+ log(`Smart Clock: Clock Out successful`);
713
+ } else if (result.alreadyDone) {
714
+ log(`Smart Clock: ${result.message}`);
715
+ } else {
716
+ log(`Smart Clock: Clock Out failed - ${result.message}`);
717
+ }
718
+ return;
719
+ }
720
+ }
721
+
722
+ // Reclock command
723
+ async function reclock() {
724
+ const config = loadConfig();
725
+ if (!config) {
726
+ console.error('Not configured. Run "auto-clock setup" first.');
727
+ process.exit(1);
728
+ }
729
+
730
+ // Try to get fresh JWT
731
+ let jwt = config.jwt;
732
+ try {
733
+ const result = await login(config.email, config.password, true);
734
+ if (result.jwt) {
735
+ jwt = result.jwt;
736
+ config.jwt = jwt;
737
+ saveConfig(config);
738
+ }
739
+ } catch (e) {
740
+ // Use existing token
741
+ }
742
+
743
+ if (!jwt) {
744
+ console.error('No JWT token. Run "auto-clock token" to set one.');
745
+ process.exit(1);
746
+ }
747
+
748
+ // Calculate defaults
749
+ const today = new Date();
750
+ const defaultDate = today.toISOString().split('T')[0]; // YYYY-MM-DD
751
+ const defaultInTime = config.clockInTime || '08:30';
752
+ const defaultOutTime = config.clockOutTime || '17:30';
753
+
754
+ console.log('\n========================================');
755
+ console.log(' Request ReClock');
756
+ console.log('========================================\n');
757
+ console.log('Press Enter to use default values shown in brackets.\n');
758
+
759
+ const dateStr = (await prompt(`Date [${defaultDate}]: `)) || defaultDate;
760
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
761
+ console.error('Invalid date format. Use YYYY-MM-DD (e.g., 2025-01-10)\n');
762
+ process.exit(1);
763
+ }
764
+
765
+ const typeInput = (await prompt('Type [in]/out: ')) || 'in';
766
+ if (!['in', 'out'].includes(typeInput.toLowerCase())) {
767
+ console.error('Invalid type. Use "in" or "out"\n');
768
+ process.exit(1);
769
+ }
770
+
771
+ const isClockIn = typeInput.toLowerCase() === 'in';
772
+ const defaultTime = isClockIn ? defaultInTime : defaultOutTime;
773
+
774
+ const time = (await prompt(`Time [${defaultTime}]: `)) || defaultTime;
775
+ if (!/^\d{2}:\d{2}$/.test(time)) {
776
+ console.error('Invalid time format. Use HH:MM (e.g., 08:30)\n');
777
+ process.exit(1);
778
+ }
779
+
780
+ const reason = (await prompt('Reason [Forgot to clock]: ')) || 'Forgot to clock';
781
+
782
+ // Date format: YYYY-MM-DDT00:00:00+07:00 (use midnight for the date part)
783
+ const date = `${dateStr}T00:00:00+07:00`;
784
+
785
+ console.log('\nSubmitting reclock request...');
786
+ console.log(` Date: ${dateStr}`);
787
+ console.log(` Time: ${time}`);
788
+ console.log(` Type: ${isClockIn ? 'Clock In' : 'Clock Out'}`);
789
+ console.log(` Reason: ${reason}`);
790
+
791
+ try {
792
+ const result = doReClock(jwt, date, time, isClockIn, config.location || 'TechBodia', reason);
793
+
794
+ if (result.ErrorCode === 0) {
795
+ console.log(`\nReClock submitted successfully!`);
796
+ console.log(`Request ID: ${result.Result.RequestId}\n`);
797
+ } else {
798
+ console.error(`\nReClock failed: ${result.ErrorMessage || JSON.stringify(result)}\n`);
799
+ }
800
+ } catch (error) {
801
+ console.error(`\nReClock failed: ${error.message}\n`);
802
+ }
803
+ }
804
+
805
+ // Reclock for a date range
806
+ async function reclockRange() {
807
+ const config = loadConfig();
808
+ if (!config) {
809
+ console.error('Not configured. Run "auto-clock setup" first.');
810
+ process.exit(1);
811
+ }
812
+
813
+ // Try to get fresh JWT
814
+ let jwt = config.jwt;
815
+ try {
816
+ const result = await login(config.email, config.password, true);
817
+ if (result.jwt) {
818
+ jwt = result.jwt;
819
+ config.jwt = jwt;
820
+ saveConfig(config);
821
+ }
822
+ } catch (e) {
823
+ // Use existing token
824
+ }
825
+
826
+ if (!jwt) {
827
+ console.error('No JWT token. Run "auto-clock token" to set one.');
828
+ process.exit(1);
829
+ }
830
+
831
+ // Calculate defaults
832
+ const today = new Date();
833
+ const defaultEndDate = today.toISOString().split('T')[0];
834
+ const thirtyDaysAgo = new Date(today);
835
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
836
+ const defaultStartDate = thirtyDaysAgo.toISOString().split('T')[0];
837
+ const defaultInTime = config.clockInTime || '08:30';
838
+ const defaultOutTime = config.clockOutTime || '17:30';
839
+
840
+ console.log('\n========================================');
841
+ console.log(' ReClock Range (Multiple Days)');
842
+ console.log('========================================\n');
843
+ console.log('This will submit reclock for each weekday in the date range.');
844
+ console.log('Press Enter to use default values shown in brackets.\n');
845
+
846
+ const startDateStr = (await prompt(`Start Date [${defaultStartDate}]: `)) || defaultStartDate;
847
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(startDateStr)) {
848
+ console.error('Invalid date format. Use YYYY-MM-DD\n');
849
+ process.exit(1);
850
+ }
851
+
852
+ const endDateStr = (await prompt(`End Date [${defaultEndDate}]: `)) || defaultEndDate;
853
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(endDateStr)) {
854
+ console.error('Invalid date format. Use YYYY-MM-DD\n');
855
+ process.exit(1);
856
+ }
857
+
858
+ const typeInput = (await prompt('Type [in]/out/both: ')) || 'in';
859
+ if (!['in', 'out', 'both'].includes(typeInput.toLowerCase())) {
860
+ console.error('Invalid type. Use "in", "out", or "both"\n');
861
+ process.exit(1);
862
+ }
863
+
864
+ const doBoth = typeInput.toLowerCase() === 'both';
865
+ const doClockIn = doBoth || typeInput.toLowerCase() === 'in';
866
+ const doClockOut = doBoth || typeInput.toLowerCase() === 'out';
867
+
868
+ let inTime = defaultInTime;
869
+ let outTime = defaultOutTime;
870
+
871
+ if (doClockIn) {
872
+ inTime = (await prompt(`Clock In Time [${defaultInTime}]: `)) || defaultInTime;
873
+ if (!/^\d{2}:\d{2}$/.test(inTime)) {
874
+ console.error('Invalid time format. Use HH:MM\n');
875
+ process.exit(1);
876
+ }
877
+ }
878
+
879
+ if (doClockOut) {
880
+ outTime = (await prompt(`Clock Out Time [${defaultOutTime}]: `)) || defaultOutTime;
881
+ if (!/^\d{2}:\d{2}$/.test(outTime)) {
882
+ console.error('Invalid time format. Use HH:MM\n');
883
+ process.exit(1);
884
+ }
885
+ }
886
+
887
+ const reason = (await prompt('Reason [Forgot to clock]: ')) || 'Forgot to clock';
888
+
889
+ // Generate list of weekdays in range
890
+ const startDate = new Date(startDateStr);
891
+ const endDate = new Date(endDateStr);
892
+ const dates = [];
893
+
894
+ for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
895
+ const dayOfWeek = d.getDay();
896
+ if (dayOfWeek !== 0 && dayOfWeek !== 6) { // Skip weekends
897
+ dates.push(d.toISOString().split('T')[0]);
898
+ }
899
+ }
900
+
901
+ if (dates.length === 0) {
902
+ console.log('\nNo weekdays found in the specified range.\n');
903
+ return;
904
+ }
905
+
906
+ console.log(`\nFound ${dates.length} weekday(s) to reclock:`);
907
+ console.log(` From: ${startDateStr}`);
908
+ console.log(` To: ${endDateStr}`);
909
+ if (doClockIn) console.log(` Clock In: ${inTime}`);
910
+ if (doClockOut) console.log(` Clock Out: ${outTime}`);
911
+ console.log(` Reason: ${reason}`);
912
+
913
+ const confirm = await prompt('\nProceed? (y/n) [y]: ');
914
+ if (confirm.toLowerCase() === 'n') {
915
+ console.log('Cancelled.\n');
916
+ return;
917
+ }
918
+
919
+ console.log('\nSubmitting reclock requests...\n');
920
+
921
+ let successCount = 0;
922
+ let failCount = 0;
923
+
924
+ for (const dateStr of dates) {
925
+ const date = `${dateStr}T00:00:00+07:00`;
926
+
927
+ if (doClockIn) {
928
+ try {
929
+ const result = doReClock(jwt, date, inTime, true, config.location || 'TechBodia', reason);
930
+ if (result.ErrorCode === 0) {
931
+ console.log(` ${dateStr} IN - OK (ID: ${result.Result.RequestId})`);
932
+ successCount++;
933
+ } else {
934
+ console.log(` ${dateStr} IN - FAILED: ${result.ErrorMessage || 'Unknown error'}`);
935
+ failCount++;
936
+ }
937
+ } catch (error) {
938
+ console.log(` ${dateStr} IN - FAILED: ${error.message}`);
939
+ failCount++;
940
+ }
941
+ }
942
+
943
+ if (doClockOut) {
944
+ try {
945
+ const result = doReClock(jwt, date, outTime, false, config.location || 'TechBodia', reason);
946
+ if (result.ErrorCode === 0) {
947
+ console.log(` ${dateStr} OUT - OK (ID: ${result.Result.RequestId})`);
948
+ successCount++;
949
+ } else {
950
+ console.log(` ${dateStr} OUT - FAILED: ${result.ErrorMessage || 'Unknown error'}`);
951
+ failCount++;
952
+ }
953
+ } catch (error) {
954
+ console.log(` ${dateStr} OUT - FAILED: ${error.message}`);
955
+ failCount++;
956
+ }
957
+ }
958
+
959
+ // Small delay to avoid rate limiting
960
+ await new Promise(resolve => setTimeout(resolve, 200));
961
+ }
962
+
963
+ console.log(`\nCompleted: ${successCount} success, ${failCount} failed\n`);
964
+ }
965
+
966
+ // Main CLI
967
+ async function main() {
968
+ const command = process.argv[2];
969
+
970
+ switch (command) {
971
+ case 'setup':
972
+ await setup();
973
+ break;
974
+
975
+ case 'token':
976
+ await setToken();
977
+ break;
978
+
979
+ case 'in': {
980
+ const inSuccess = await performClock(true);
981
+ process.exit(inSuccess ? 0 : 1);
982
+ }
983
+
984
+ case 'out': {
985
+ const outSuccess = await performClock(false);
986
+ process.exit(outSuccess ? 0 : 1);
987
+ }
988
+
989
+ case 'schedule':
990
+ await setSchedule();
991
+ break;
992
+
993
+ case 'cron':
994
+ showCron();
995
+ break;
996
+
997
+ case 'status':
998
+ showStatus();
999
+ break;
1000
+
1001
+ case 'logs':
1002
+ showLogs();
1003
+ break;
1004
+
1005
+ case 'history': {
1006
+ const days = parseInt(process.argv[3]) || 14;
1007
+ await showHistory(days);
1008
+ break;
1009
+ }
1010
+
1011
+ case 'reclock':
1012
+ await reclock();
1013
+ break;
1014
+
1015
+ case 'reclock-range':
1016
+ await reclockRange();
1017
+ break;
1018
+
1019
+ case 'enable':
1020
+ enableAutoClock();
1021
+ break;
1022
+
1023
+ case 'disable':
1024
+ disableAutoClock();
1025
+ break;
1026
+
1027
+ case 'smart':
1028
+ case 'auto':
1029
+ await smartClock();
1030
+ break;
1031
+
1032
+ case 'help':
1033
+ case '--help':
1034
+ case '-h':
1035
+ showHelp();
1036
+ break;
1037
+
1038
+ default:
1039
+ if (command) {
1040
+ console.error(`Unknown command: ${command}\n`);
1041
+ }
1042
+ showHelp();
1043
+ break;
1044
+ }
1045
+ }
1046
+
1047
+ main().catch((err) => {
1048
+ console.error('Error:', err.message);
1049
+ process.exit(1);
1050
+ });
@@ -0,0 +1,5 @@
1
+ {
2
+ "email": "your.email@techbodia.com",
3
+ "password": "your-password",
4
+ "location": "TechBodia"
5
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "auto-clock-cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool for automatic clock in/out with HRMS integration",
5
+ "main": "clock.js",
6
+ "bin": {
7
+ "auto-clock": "./clock.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node clock.js help"
11
+ },
12
+ "keywords": [
13
+ "clock",
14
+ "attendance",
15
+ "hrms",
16
+ "cli",
17
+ "auto-clock",
18
+ "clock-in",
19
+ "clock-out",
20
+ "time-tracking"
21
+ ],
22
+ "author": "TechBodia",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/techbodia/auto-clock.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=14.0.0"
30
+ },
31
+ "files": [
32
+ "clock.js",
33
+ "README.md",
34
+ "config.example.json"
35
+ ]
36
+ }
package/readme.md ADDED
@@ -0,0 +1,96 @@
1
+ # Auto Clock CLI
2
+
3
+ A CLI tool to automatically clock in and clock out at scheduled times.
4
+
5
+ ## Installation
6
+
7
+ ### Option 1: Install globally from npm (for team members)
8
+ ```bash
9
+ npm install -g auto-clock
10
+ ```
11
+
12
+ ### Option 2: Install from local folder
13
+ ```bash
14
+ cd /path/to/auto-clock
15
+ npm install -g .
16
+ ```
17
+
18
+ ### Option 3: Run directly without installing
19
+ ```bash
20
+ node /path/to/auto-clock/clock.js <command>
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Setup (first-time users)
26
+ ```bash
27
+ auto-clock setup
28
+ ```
29
+
30
+ This will prompt you for:
31
+ - Email
32
+ - Password
33
+ - Location (default: TechBodia)
34
+ - Clock in time (e.g., 08:00)
35
+ - Clock out time (e.g., 17:00)
36
+
37
+ ### 2. Test manually
38
+ ```bash
39
+ auto-clock in # Clock in now
40
+ auto-clock out # Clock out now
41
+ ```
42
+
43
+ ### 3. Set up automatic scheduling
44
+ ```bash
45
+ auto-clock cron # Shows cron commands to copy
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ | Command | Description |
51
+ |---------|-------------|
52
+ | `auto-clock setup` | Interactive setup wizard |
53
+ | `auto-clock in` | Clock in now |
54
+ | `auto-clock out` | Clock out now |
55
+ | `auto-clock schedule` | Change clock in/out times |
56
+ | `auto-clock cron` | Show cron setup instructions |
57
+ | `auto-clock status` | Show current configuration |
58
+ | `auto-clock logs` | Show recent logs |
59
+ | `auto-clock help` | Show help |
60
+
61
+ ## Configuration
62
+
63
+ Config is stored at: `~/.auto-clock/config.json`
64
+ Logs are stored at: `~/.auto-clock/clock.log`
65
+
66
+ ## Scheduling with Cron (macOS/Linux)
67
+
68
+ After running `auto-clock cron`, copy the displayed commands to your crontab:
69
+
70
+ ```bash
71
+ crontab -e
72
+ ```
73
+
74
+ Example cron entries (8:00 AM clock in, 5:00 PM clock out, Mon-Fri):
75
+ ```cron
76
+ 0 8 * * 1-5 /path/to/node /path/to/clock.js in >> ~/.auto-clock/clock.log 2>&1
77
+ 0 17 * * 1-5 /path/to/node /path/to/clock.js out >> ~/.auto-clock/clock.log 2>&1
78
+ ```
79
+
80
+ **Note:** Your computer must be ON and awake at scheduled times.
81
+
82
+ ## For Multiple Users
83
+
84
+ Each user:
85
+ 1. Installs the tool: `npm install -g auto-clock`
86
+ 2. Runs setup: `auto-clock setup`
87
+ 3. Configures cron: `auto-clock cron`
88
+
89
+ Each user's config is stored separately in their home directory.
90
+
91
+ ## Uninstall
92
+
93
+ ```bash
94
+ npm uninstall -g auto-clock
95
+ rm -rf ~/.auto-clock
96
+ ```