@mrselva/attendance 1.0.0 → 1.1.2

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 +23 -42
  2. package/index.js +171 -50
  3. package/package.json +10 -4
package/README.md CHANGED
@@ -4,15 +4,15 @@
4
4
  [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
5
5
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D16.0.0-brightgreen.svg)](https://nodejs.org)
6
6
 
7
- > An automated CLI converter that extracts vertical-wise attendance data from KG-KITE / SoI multi-sheet attendance spreadsheets, generates DigiCampus-compliant upload workbooks, and bundles them into clean ZIP archives.
7
+ > A fast and lightweight CLI tool to extract, normalize, and export multi-sheet attendance spreadsheets into organized Excel files and ZIP archives.
8
8
 
9
- Run instantly with **`npx`** — no cloning or installation required!
9
+ Run instantly with **`npx`** — no installation required!
10
10
 
11
11
  ---
12
12
 
13
13
  ## ⚡ Quick Start
14
14
 
15
- Open your terminal in any folder containing your attendance spreadsheet (or an `input/` folder):
15
+ Open your terminal in any directory and run:
16
16
 
17
17
  ```bash
18
18
  npx @mrselva/attendance
@@ -29,64 +29,51 @@ attendance
29
29
 
30
30
  ## ✨ Features
31
31
 
32
- - **Zero Configuration**: Automatically discovers `.xlsx` or `.xls` spreadsheets in `./input` or the current working directory.
33
- - **Interactive CLI Wizard**: Clean, color-coded terminal prompts to pick verticals and date ranges.
34
- - **Comprehensive Vertical Support**: Detects lab vertical sheets:
35
- - `AD`: AI & Data Science Lab
36
- - `BW`: Blockchain and Web3.0 Lab
37
- - `CD`: Cloud and DevOps Lab
38
- - `CS`: Cyber Security Lab
39
- - `DMA`: Data & Mobile Analytics Lab
40
- - `EI`: Embedded & IoT Lab
41
- - `FW`: Full Stack Web Lab
42
- - `SOAL`: School of AI Learning
43
- - `SOP`: School of Programming
32
+ - **Zero Configuration & Auto-Import**: If no file is in `./input/`, an interactive file picker window opens so you can select your spreadsheet directly from your computer, or drag & drop file paths into the terminal.
33
+ - **Interactive CLI Wizard**: Clean, color-coded terminal prompts to pick sheets and date filters.
34
+ - **Multi-Sheet Detection**: Automatically discovers all relevant sheets and rosters across the workbook.
44
35
  - **Smart Date Filtering**:
45
- - Export **All recorded dates** (creates one upload file per instructional date)
46
- - Export a **Single date** (e.g. `2024-09-15`)
36
+ - Export **All recorded dates**
37
+ - Export a **Single date** (e.g. `YYYY-MM-DD`)
47
38
  - Export a **Date range** (from `YYYY-MM-DD` to `YYYY-MM-DD`)
48
39
  - Export the **Latest date only**
49
- - **DigiCampus Compatibility**: Creates individual files with a `Students` sheet formatted with:
50
- - `Email Id`
51
- - `Registration Id`
52
- - `Attendance *` (`PRESENT` or `ABSENT`)
53
- - **Status Normalization**: Accurately normalizes marks (`P`, `1`, `OD`, `ELITE` → `PRESENT`; `A`, `0`, unrecorded → `ABSENT`).
54
- - **Automatic ZIP Packaging**: Compresses generated files directly into `./output/<Vertical>_Attendance.zip`.
40
+ - **Status Normalization**: Accurately normalizes attendance status (`PRESENT`, `ABSENT`, etc.) and handles blanks cleanly.
41
+ - **Automatic ZIP Packaging**: Compresses generated export files directly into clean ZIP archives in `./output/`.
55
42
 
56
43
  ---
57
44
 
58
45
  ## 🚀 CLI Usage & Flags
59
46
 
60
- You can run the interactive wizard or pass non-interactive CLI flags for automation:
47
+ Run the interactive wizard or pass flags for automation:
61
48
 
62
49
  ```bash
63
50
  # Interactive mode
64
51
  npx @mrselva/attendance
65
52
 
66
- # Export ALL dates for a specific vertical
67
- npx @mrselva/attendance -v BW -a
53
+ # Export all dates for a specific sheet
54
+ npx @mrselva/attendance -v SHEET_CODE -a
68
55
 
69
56
  # Export a single date
70
- npx @mrselva/attendance -v CD -d 2024-09-12
57
+ npx @mrselva/attendance -v SHEET_CODE -d 2024-09-12
71
58
 
72
59
  # Export the latest date recorded
73
- npx @mrselva/attendance -v AD --latest
60
+ npx @mrselva/attendance -v SHEET_CODE --latest
74
61
 
75
- # Process ALL verticals at once
62
+ # Process ALL sheets at once
76
63
  npx @mrselva/attendance -v ALL -a
77
64
 
78
65
  # Specify a custom input spreadsheet file
79
- npx @mrselva/attendance -f "2024-28_SoI_Attendance_Sheet.xlsx" -v FW -a
66
+ npx @mrselva/attendance -f "attendance_sheet.xlsx" -v SHEET_CODE -a
80
67
  ```
81
68
 
82
69
  ### Options Reference
83
70
 
84
71
  | Flag | Long Flag | Description |
85
72
  | :--- | :--- | :--- |
86
- | `-v <code\|ALL>` | `--vertical <code\|ALL>` | Vertical code (e.g. `AD`, `BW`, `CD`, `CS`, `DMA`, `EI`, `FW`, or `ALL`) |
87
- | `-a` | `--all` | Export all recorded instructional dates |
73
+ | `-v <code>` | `--vertical <code>` | Sheet code or name (or `ALL`) |
74
+ | `-a` | `--all` | Export all recorded dates |
88
75
  | `-d <date>` | `--date <YYYY-MM-DD>` | Export a specific date |
89
- | | `--latest` | Export the latest instructional date |
76
+ | | `--latest` | Export the latest recorded date |
90
77
  | `-f <file>` | `--file <path>` | Specify input Excel file path or filename |
91
78
  | `-h` | `--help` | Display usage and help message |
92
79
 
@@ -96,24 +83,18 @@ npx @mrselva/attendance -f "2024-28_SoI_Attendance_Sheet.xlsx" -v FW -a
96
83
 
97
84
  ```text
98
85
  your-folder/
99
- ├── input/ # (Optional) Place source spreadsheets here
100
- │ └── 2024-28_SoI_Attendance.xlsx
101
- └── output/ # Automatically generated output
102
- ├── BW_Attendance.zip
103
- └── CD_Attendance.zip
86
+ ├── input/ # Place source spreadsheets here (or auto-import)
87
+ └── output/ # Automatically generated ZIP archives
104
88
  ```
105
89
 
106
- > **Note:** If an `input/` folder is not found, the CLI will look for Excel files directly in your current working directory.
107
-
108
90
  ---
109
91
 
110
92
  ## 🛠 Requirements
111
93
 
112
94
  - **Node.js**: `v16.0.0` or higher
113
- - **npm**: `v7.0.0` or higher (included with Node.js)
114
95
 
115
96
  ---
116
97
 
117
98
  ## 📄 License
118
99
 
119
- [ISC](LICENSE) &copy; [mrselva-eth](https://github.com/mrselva-eth)
100
+ [ISC](LICENSE) &copy; [mrselva](https://github.com/mrselva)
package/index.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Attendance to DigiCampus Converter
3
+ * Attendance Spreadsheet Converter & Exporter
4
4
  * Single-file Node.js CLI script
5
5
  *
6
6
  * Features:
7
- * - Scans ./input for attendance spreadsheets (.xlsx / .xls)
8
- * - Interactively prompts for vertical selection (AD, BW, CD, CS, DMA, EI, FW, etc.)
7
+ * - Scans ./input or current directory for attendance spreadsheets (.xlsx / .xls)
8
+ * - Interactively prompts for sheet/category selection
9
9
  * - Allows exporting all recorded dates, specific date, range, or latest date
10
- * - Generates individual DigiCampus-compliant Excel files ('Students' sheet with Email Id, Registration Id, Attendance *)
11
- * - Automatically packages all generated files into a ZIP archive in ./output
12
- * - Works both interactively (in terminal) and with optional CLI flags (--vertical, --all, etc.)
10
+ * - Generates individual formatted Excel files with ZIP archive in ./output
11
+ * - Works both interactively (in terminal) and with CLI flags (--vertical, --all, etc.)
13
12
  */
14
13
 
15
14
  const fs = require('fs');
16
15
  const path = require('path');
17
16
  const readline = require('readline');
17
+ const { execFileSync } = require('child_process');
18
18
  const xlsx = require('xlsx');
19
19
  const AdmZip = require('adm-zip');
20
20
 
@@ -22,18 +22,8 @@ const AdmZip = require('adm-zip');
22
22
  const INPUT_DIR = path.resolve(process.cwd(), 'input');
23
23
  const OUTPUT_DIR = path.resolve(process.cwd(), 'output');
24
24
 
25
- // Known vertical full names mapping
26
- const VERTICAL_NAMES = {
27
- 'AD': 'AI & Data Science Lab',
28
- 'BW': 'Blockchain and Web3.0 Lab',
29
- 'CD': 'Cloud and DevOps Lab',
30
- 'CS': 'Cyber Security Lab',
31
- 'DMA': 'Data & Mobile Analytics Lab',
32
- 'EI': 'Embedded & IoT Lab',
33
- 'FW': 'Full Stack Web Lab',
34
- 'SOAL': 'School of AI Learning',
35
- 'SOP': 'School of Programming'
36
- };
25
+ // Optional category/sheet code to display name mapping
26
+ const VERTICAL_NAMES = {};
37
27
 
38
28
  // ANSI color helpers
39
29
  const colors = {
@@ -200,7 +190,6 @@ function parseVerticalSheet(ws, sheetName) {
200
190
  let nameColIdx = -1;
201
191
  let emailColIdx = -1;
202
192
  let rollColIdx = -1;
203
- let isKiteEmail = false;
204
193
  const dateColumns = [];
205
194
 
206
195
  // Analyze header row
@@ -214,13 +203,8 @@ function parseVerticalSheet(ws, sheetName) {
214
203
  regColIdx = c;
215
204
  } else if (hStr.includes('student name') || hStr === 'name') {
216
205
  nameColIdx = c;
217
- } else if (hStr.includes('kite email')) {
206
+ } else if (hStr.includes('email id') || hStr.includes('email') || hStr.includes('mail')) {
218
207
  emailColIdx = c;
219
- isKiteEmail = true;
220
- } else if (!isKiteEmail && (hStr.includes('email id') || hStr.includes('email'))) {
221
- if (!hStr.includes('soi')) {
222
- emailColIdx = c;
223
- }
224
208
  } else if (hStr.includes('roll number') || hStr.includes('roll no')) {
225
209
  rollColIdx = c;
226
210
  } else {
@@ -269,7 +253,7 @@ function parseVerticalSheet(ws, sheetName) {
269
253
  name: name,
270
254
  regNo: regNo,
271
255
  rollNo: rollNo,
272
- email: email || (rollNo ? `${rollNo.toLowerCase()}@kgkite.ac.in` : '')
256
+ email: email || ''
273
257
  });
274
258
  }
275
259
 
@@ -314,11 +298,11 @@ function parseVerticalSheet(ws, sheetName) {
314
298
  };
315
299
  }
316
300
 
317
- // Generate DigiCampus Excel workbook for a single date
318
- function generateDigiCampusWorkbook(verticalData, dateItem, rows) {
301
+ // Generate formatted Excel workbook for a single date
302
+ function generateExportWorkbook(verticalData, dateItem, rows) {
319
303
  const wb = xlsx.utils.book_new();
320
304
 
321
- // DigiCampus Template Header
305
+ // Export Sheet Header
322
306
  const wsData = [
323
307
  ['Email Id', 'Registration Id', 'Attendance *']
324
308
  ];
@@ -387,6 +371,128 @@ function parseArgs() {
387
371
  return flags;
388
372
  }
389
373
 
374
+ // Clean user input file path (strip quotes and shell prefixes)
375
+ function cleanPath(input) {
376
+ if (!input) return '';
377
+ let str = input.trim();
378
+ if (str.startsWith('&')) {
379
+ str = str.slice(1).trim();
380
+ }
381
+ if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
382
+ str = str.slice(1, -1).trim();
383
+ }
384
+ return str;
385
+ }
386
+
387
+ // Open native OS file picker dialog
388
+ function openFileDialog() {
389
+ const platform = process.platform;
390
+ try {
391
+ if (platform === 'win32') {
392
+ const psScript = `
393
+ Add-Type -AssemblyName System.Windows.Forms
394
+ $dlg = New-Object System.Windows.Forms.OpenFileDialog
395
+ $dlg.Filter = 'Excel Files (*.xlsx;*.xls)|*.xlsx;*.xls|All Files (*.*)|*.*'
396
+ $dlg.Title = 'Select Attendance Spreadsheet'
397
+ $dlg.RestoreDirectory = $true
398
+ $result = $dlg.ShowDialog()
399
+ if ($result -eq [System.Windows.Forms.DialogResult]::OK) {
400
+ Write-Output $dlg.FileName
401
+ }
402
+ `;
403
+ const output = execFileSync('powershell.exe', ['-NoProfile', '-STA', '-Command', psScript], {
404
+ encoding: 'utf8',
405
+ stdio: ['ignore', 'pipe', 'ignore'],
406
+ timeout: 180000
407
+ });
408
+ return output ? output.trim() : null;
409
+ } else if (platform === 'darwin') {
410
+ const appleScript = 'POSIX path of (choose file with prompt "Select Attendance Spreadsheet" of type {"xlsx", "xls"})';
411
+ const output = execFileSync('osascript', ['-e', appleScript], {
412
+ encoding: 'utf8',
413
+ stdio: ['ignore', 'pipe', 'ignore']
414
+ });
415
+ return output ? output.trim() : null;
416
+ } else {
417
+ const output = execFileSync('zenity', ['--file-selection', '--file-filter=Excel files | *.xlsx *.xls', '--title=Select Attendance Spreadsheet'], {
418
+ encoding: 'utf8',
419
+ stdio: ['ignore', 'pipe', 'ignore']
420
+ });
421
+ return output ? output.trim() : null;
422
+ }
423
+ } catch (e) {
424
+ return null;
425
+ }
426
+ }
427
+
428
+ // Copy / import a selected spreadsheet into ./input/
429
+ function importFileToInput(srcPath) {
430
+ ensureDirs();
431
+ const fileName = path.basename(srcPath);
432
+ const destPath = path.join(INPUT_DIR, fileName);
433
+
434
+ if (path.resolve(srcPath) !== path.resolve(destPath)) {
435
+ try {
436
+ fs.copyFileSync(srcPath, destPath);
437
+ logSuccess(`Imported "${fileName}" into ./input/ folder!`);
438
+ return {
439
+ name: fileName,
440
+ fullPath: destPath,
441
+ relativePath: path.join('input', fileName)
442
+ };
443
+ } catch (err) {
444
+ logWarn(`Could not copy into ./input/ (${err.message}). Reading from original location.`);
445
+ return {
446
+ name: fileName,
447
+ fullPath: srcPath,
448
+ relativePath: srcPath
449
+ };
450
+ }
451
+ }
452
+
453
+ return {
454
+ name: fileName,
455
+ fullPath: destPath,
456
+ relativePath: path.relative(process.cwd(), destPath) || path.join('input', fileName)
457
+ };
458
+ }
459
+
460
+ // Interactive prompt for file selection when needed
461
+ async function promptForFile(lines) {
462
+ logHeader('Select Attendance Spreadsheet');
463
+ console.log('No attendance spreadsheet selected yet.');
464
+ console.log('Please choose how you want to select your Excel file (.xlsx / .xls):\n');
465
+ console.log(` [${colors.bold}1${colors.reset}] ${colors.green}Browse file${colors.reset} (Opens file picker window to click your file) [Default]`);
466
+ console.log(` [${colors.bold}2${colors.reset}] ${colors.cyan}Enter or drag & drop file path here${colors.reset}`);
467
+
468
+ while (true) {
469
+ const choice = (await askQuestion(lines, `\nChoose option [1 or 2] (default: 1): `)).trim();
470
+
471
+ if (choice === '2') {
472
+ const manual = await askQuestion(lines, 'Enter or drag & drop file path: ');
473
+ const cleaned = cleanPath(manual);
474
+ if (cleaned && fs.existsSync(cleaned) && fs.statSync(cleaned).isFile()) {
475
+ const ext = path.extname(cleaned).toLowerCase();
476
+ if (ext === '.xlsx' || ext === '.xls') {
477
+ return cleaned;
478
+ } else {
479
+ logWarn(`"${cleaned}" is not an Excel spreadsheet (.xlsx / .xls).`);
480
+ }
481
+ } else {
482
+ logError(`File not found at: "${cleaned}". Please check the path and try again.`);
483
+ }
484
+ } else {
485
+ console.log(`${colors.dim}Opening file picker dialog... Please select your file in the window.${colors.reset}`);
486
+ const picked = openFileDialog();
487
+ if (picked && fs.existsSync(picked) && fs.statSync(picked).isFile()) {
488
+ return picked;
489
+ }
490
+ logWarn('No file was selected in the dialog.');
491
+ console.log(`You can try browsing again [1], or enter/drag & drop the file path directly [2]:`);
492
+ }
493
+ }
494
+ }
495
+
390
496
  // Interactive prompt helper
391
497
  async function askQuestion(lineIterator, promptText) {
392
498
  process.stdout.write(promptText);
@@ -419,7 +525,7 @@ Running without options launches the interactive CLI wizard.
419
525
  }
420
526
 
421
527
  console.log(`\n${colors.bold}${colors.magenta}╔═════════════════════════════════════════════════════════════════════╗`);
422
- console.log(`║ KG-KITE / SoI Attendance -> DigiCampus Exporter ║`);
528
+ console.log(`║ Attendance Sheet Exporter & Converter ║`);
423
529
  console.log(`╚═════════════════════════════════════════════════════════════════════╝${colors.reset}`);
424
530
 
425
531
  ensureDirs();
@@ -437,38 +543,53 @@ Running without options launches the interactive CLI wizard.
437
543
  if (flags.file) {
438
544
  const directPath = path.resolve(process.cwd(), flags.file);
439
545
  const inInputPath = path.join(INPUT_DIR, flags.file);
546
+ let resolvedSrc = null;
440
547
  if (fs.existsSync(directPath) && fs.statSync(directPath).isFile()) {
441
- selectedFileObj = { name: path.basename(directPath), fullPath: directPath, relativePath: flags.file };
548
+ resolvedSrc = directPath;
442
549
  } else if (fs.existsSync(inInputPath) && fs.statSync(inInputPath).isFile()) {
443
- selectedFileObj = { name: path.basename(inInputPath), fullPath: inInputPath, relativePath: path.join('input', flags.file) };
550
+ resolvedSrc = inInputPath;
444
551
  } else {
445
552
  logError(`Specified file "${flags.file}" was not found.`);
446
553
  rl.close();
447
554
  process.exit(1);
448
555
  }
556
+ selectedFileObj = importFileToInput(resolvedSrc);
449
557
  } else {
450
558
  const inputFiles = findInputFiles();
451
- if (inputFiles.length === 0) {
452
- logError(`No Excel spreadsheets (.xlsx / .xls) found in: ${INPUT_DIR} or current directory.`);
453
- console.log(`\n${colors.yellow}Please place your attendance sheet (e.g. 2024-28_SoI_Attendance_Sheet.xlsx) in the 'input' folder or current directory and run this script again.${colors.reset}\n`);
454
- rl.close();
455
- process.exit(1);
456
- }
457
559
 
458
- if (inputFiles.length > 1) {
560
+ if (inputFiles.length === 0) {
561
+ // Auto prompt to browse or drag & drop when input/ is empty
562
+ const chosen = await promptForFile(lines);
563
+ selectedFileObj = importFileToInput(chosen);
564
+ } else if (inputFiles.length === 1) {
565
+ logInfo(`Found existing attendance spreadsheet: ${colors.bold}${inputFiles[0].relativePath}${colors.reset}`);
566
+ console.log(` [${colors.bold}1${colors.reset}] Use this spreadsheet [Default]`);
567
+ console.log(` [${colors.bold}2${colors.reset}] Choose/import a different file`);
568
+ const pick = (await askQuestion(lines, `\nSelect option [1 or 2] (default: 1): `)).trim();
569
+ if (pick === '2') {
570
+ const chosen = await promptForFile(lines);
571
+ selectedFileObj = importFileToInput(chosen);
572
+ } else {
573
+ selectedFileObj = inputFiles[0];
574
+ }
575
+ } else {
459
576
  logHeader('Select Input Attendance File');
460
577
  inputFiles.forEach((fileObj, idx) => {
461
578
  console.log(` [${colors.bold}${idx + 1}${colors.reset}] ${fileObj.relativePath}`);
462
579
  });
463
- const ans = await askQuestion(lines, `\nSelect file [1-${inputFiles.length}] (default: 1): `);
464
- const num = parseInt(ans.trim(), 10);
465
- if (!isNaN(num) && num >= 1 && num <= inputFiles.length) {
466
- selectedFileObj = inputFiles[num - 1];
580
+ console.log(` [${colors.bold}N${colors.reset}] ${colors.cyan}Import a new spreadsheet${colors.reset} (Browse or drag & drop)`);
581
+ const ans = (await askQuestion(lines, `\nSelect file [1-${inputFiles.length}] or N (default: 1): `)).trim();
582
+ if (ans.toUpperCase() === 'N') {
583
+ const chosen = await promptForFile(lines);
584
+ selectedFileObj = importFileToInput(chosen);
467
585
  } else {
468
- selectedFileObj = inputFiles[0];
586
+ const num = parseInt(ans, 10);
587
+ if (!isNaN(num) && num >= 1 && num <= inputFiles.length) {
588
+ selectedFileObj = inputFiles[num - 1];
589
+ } else {
590
+ selectedFileObj = inputFiles[0];
591
+ }
469
592
  }
470
- } else {
471
- selectedFileObj = inputFiles[0];
472
593
  }
473
594
  }
474
595
 
@@ -610,10 +731,10 @@ Running without options launches the interactive CLI wizard.
610
731
  rl.close();
611
732
 
612
733
  // Process selected verticals
613
- logHeader('Processing & Generating DigiCampus Files');
734
+ logHeader('Processing & Generating Export Files');
614
735
 
615
736
  for (const vert of targets) {
616
- console.log(`\n${colors.bold}Vertical: ${vert.sheetName} - ${vert.verticalName}${colors.reset}`);
737
+ console.log(`\n${colors.bold}Sheet: ${vert.sheetName}${vert.verticalName !== vert.sheetName ? ' - ' + vert.verticalName : ''}${colors.reset}`);
617
738
  console.log(` Students: ${colors.bold}${vert.students.length}${colors.reset}`);
618
739
 
619
740
  let datesToExport = [...vert.dates];
@@ -651,7 +772,7 @@ Running without options launches the interactive CLI wizard.
651
772
 
652
773
  for (const dateItem of datesToExport) {
653
774
  const fileName = `Upload_Attendance_${vert.sheetName}_${dateItem.dateStr}.xlsx`;
654
- const outWb = generateDigiCampusWorkbook(vert, dateItem, rows);
775
+ const outWb = generateExportWorkbook(vert, dateItem, rows);
655
776
  const fileBuffer = xlsx.write(outWb, { type: 'buffer', bookType: 'xlsx' });
656
777
  zip.addFile(fileName, fileBuffer);
657
778
  }
@@ -670,7 +791,7 @@ Running without options launches the interactive CLI wizard.
670
791
  const zipStat = fs.statSync(zipFilePath);
671
792
  const zipSizeKb = (zipStat.size / 1024).toFixed(1);
672
793
 
673
- logSuccess(`Generated ZIP archive containing ${datesToExport.length} DigiCampus files:`);
794
+ logSuccess(`Generated ZIP archive containing ${datesToExport.length} export files:`);
674
795
  console.log(` ${colors.bold}${colors.green}${zipFilePath}${colors.reset} (${zipSizeKb} KB)`);
675
796
  }
676
797
 
package/package.json CHANGED
@@ -1,19 +1,25 @@
1
1
  {
2
2
  "name": "@mrselva/attendance",
3
- "version": "1.0.0",
4
- "description": "Extract vertical attendance data and export into DigiCampus Excel templates with ZIP archive",
3
+ "version": "1.1.2",
4
+ "description": "Extract multi-sheet attendance data and export formatted Excel sheets with ZIP archives",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node index.js"
8
8
  },
9
- "keywords": ["attendance", "digicampus", "excel", "cli", "npx"],
9
+ "keywords": [
10
+ "attendance",
11
+ "excel",
12
+ "spreadsheet",
13
+ "cli",
14
+ "npx"
15
+ ],
10
16
  "author": "mrselva",
11
17
  "license": "ISC",
12
18
  "publishConfig": {
13
19
  "access": "public"
14
20
  },
15
21
  "bin": {
16
- "attendance": "./index.js"
22
+ "attendance": "index.js"
17
23
  },
18
24
  "files": [
19
25
  "index.js",