laporan-generator 2.6.2 → 2.8.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 (36) hide show
  1. package/.agents/skills/laporan-generator/SKILL.md +109 -109
  2. package/.agents/skills/laporan-generator/resources/cover.md +18 -14
  3. package/.agents/skills/laporan-generator/resources/docx.lua +65 -0
  4. package/.agents/skills/laporan-generator/resources/laporan +37 -4
  5. package/.agents/skills/laporan-generator/resources/laporan.ps1 +74 -28
  6. package/.agents/skills/laporan-generator/resources/metadata.yml +13 -16
  7. package/.agents/skills/laporan-generator/resources/scripts/bundle.py +11 -2
  8. package/.agents/skills/laporan-generator/resources/scripts/report-doctor.py +121 -48
  9. package/.agents/skills/laporan-generator/resources/scripts/report-stats.py +53 -2
  10. package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +160 -2
  11. package/.agents/skills/laporan-generator/resources/template.typ +3 -0
  12. package/.agents/skills/laporan-generator/scripts/bundle.py +11 -2
  13. package/.agents/skills/laporan-generator/scripts/finalize-docx.py +140 -1
  14. package/.agents/skills/laporan-generator/scripts/make-reference-docx.py +42 -2
  15. package/.agents/skills/laporan-generator/scripts/report-doctor.py +121 -48
  16. package/.agents/skills/laporan-generator/scripts/report-stats.py +53 -2
  17. package/.agents/skills/laporan-generator/scripts/test_scripts.py +185 -2
  18. package/README.md +49 -0
  19. package/bin/laporan-generator.js +15 -3
  20. package/bin/mcp-server.js +4 -0
  21. package/cover.md +18 -14
  22. package/docs/metadata-schema.md +2 -0
  23. package/docx.lua +65 -0
  24. package/laporan +37 -4
  25. package/laporan.ps1 +50 -11
  26. package/lib/init.js +139 -45
  27. package/lib/mcp.js +579 -0
  28. package/lib/sync-hosts.js +196 -68
  29. package/lib/uninstall.js +21 -0
  30. package/metadata.yml +13 -16
  31. package/package.json +9 -3
  32. package/scripts/bundle.py +11 -2
  33. package/scripts/report-doctor.py +121 -48
  34. package/scripts/report-stats.py +46 -2
  35. package/scripts/test_scripts.py +147 -3
  36. package/template.typ +3 -0
package/laporan.ps1 CHANGED
@@ -9,7 +9,9 @@ param (
9
9
  [Parameter(Position=2)]
10
10
  [string]$Arg1 = "",
11
11
  [Parameter(Position=3)]
12
- [string]$Arg2 = ""
12
+ [string]$Arg2 = "",
13
+ [Parameter(ValueFromRemainingArguments=$true)]
14
+ [string[]]$RemainingArgs
13
15
  )
14
16
 
15
17
  $ErrorActionPreference = "Stop"
@@ -38,7 +40,7 @@ function Get-PythonCommand {
38
40
 
39
41
  function Show-Banner {
40
42
  Write-Host " ========================================================" -ForegroundColor Cyan
41
- Write-Host " LAPORAN GENERATOR CLI v2.6.2 " -ForegroundColor Cyan
43
+ Write-Host " LAPORAN GENERATOR CLI v2.8.0 " -ForegroundColor Cyan
42
44
  Write-Host " Otomatisasi Dokumen Akademik (Typst + DOCX Engine) " -ForegroundColor Cyan
43
45
  Write-Host " ========================================================" -ForegroundColor Cyan
44
46
  Write-Host ""
@@ -453,27 +455,30 @@ function Cmd-Clean {
453
455
  }
454
456
 
455
457
  function Cmd-Stats {
458
+ param([string[]]$ArgsList)
456
459
  $pyCmd = Get-PythonCommand
457
460
  if ($pyCmd) {
458
- & $pyCmd scripts/report-stats.py
461
+ & $pyCmd scripts/report-stats.py @ArgsList
459
462
  } else {
460
463
  Write-Host "Error: Python 3 tidak ditemukan." -ForegroundColor Red
461
464
  }
462
465
  }
463
466
 
464
467
  function Cmd-Doctor {
468
+ param([string[]]$ArgsList)
465
469
  $pyCmd = Get-PythonCommand
466
470
  if ($pyCmd) {
467
- & $pyCmd scripts/report-doctor.py
471
+ & $pyCmd scripts/report-doctor.py @ArgsList
468
472
  } else {
469
473
  Write-Host "Error: Python 3 tidak ditemukan." -ForegroundColor Red
470
474
  }
471
475
  }
472
476
 
473
477
  function Cmd-Bundle {
478
+ param([string[]]$ArgsList)
474
479
  $pyCmd = Get-PythonCommand
475
480
  if ($pyCmd) {
476
- & $pyCmd scripts/bundle.py
481
+ & $pyCmd scripts/bundle.py @ArgsList
477
482
  } else {
478
483
  Write-Host "Error: Python 3 tidak ditemukan." -ForegroundColor Red
479
484
  }
@@ -525,25 +530,59 @@ function Cmd-Watch {
525
530
  }
526
531
  }
527
532
 
533
+ $extraArgs = @()
534
+ if ($SubCommand) { $extraArgs += $SubCommand }
535
+ if ($Arg1) { $extraArgs += $Arg1 }
536
+ if ($Arg2) { $extraArgs += $Arg2 }
537
+ if ($RemainingArgs) { $extraArgs += $RemainingArgs }
538
+
528
539
  switch ($Command.ToLower()) {
529
540
  "build" { Cmd-Build }
530
541
  "pdf" { Cmd-Build-PDF }
531
542
  "docx" { Cmd-Build-DOCX }
532
543
  "init" { Cmd-Init }
533
- "stats" { Cmd-Stats }
534
- "doctor" { Cmd-Doctor }
535
- "bundle" { Cmd-Bundle }
536
- "setup" { & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "scripts\setup-deps.ps1") }
544
+ "stats" { Cmd-Stats $extraArgs }
545
+ "doctor" { Cmd-Doctor $extraArgs }
546
+ "bundle" { Cmd-Bundle $extraArgs }
547
+ "setup" { & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "scripts\setup-deps.ps1") }
548
+ "mcp" {
549
+ if (Get-Command "node" -ErrorAction SilentlyContinue) {
550
+ $mcpPath = Join-Path $PSScriptRoot "bin\mcp-server.js"
551
+ if (Test-Path $mcpPath) {
552
+ node $mcpPath @extraArgs
553
+ } elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
554
+ npx -y laporan-generator mcp @extraArgs
555
+ } else {
556
+ Write-Host "bin\mcp-server.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
557
+ }
558
+ } else {
559
+ Write-Host "Node.js diperlukan untuk menjalankan MCP Server." -ForegroundColor Yellow
560
+ }
561
+ }
537
562
  "sync-skills" {
538
563
  if (Get-Command "node" -ErrorAction SilentlyContinue) {
539
- node (Join-Path $PSScriptRoot "bin\laporan-generator.js") sync-hosts
564
+ $genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
565
+ if (Test-Path $genPath) {
566
+ node $genPath sync-hosts @extraArgs
567
+ } elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
568
+ npx -y laporan-generator sync-hosts @extraArgs
569
+ } else {
570
+ Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
571
+ }
540
572
  } else {
541
573
  Write-Host "Node.js diperlukan untuk sinkronisasi multi-host AI agent." -ForegroundColor Yellow
542
574
  }
543
575
  }
544
576
  "sync-hosts" {
545
577
  if (Get-Command "node" -ErrorAction SilentlyContinue) {
546
- node (Join-Path $PSScriptRoot "bin\laporan-generator.js") sync-hosts
578
+ $genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
579
+ if (Test-Path $genPath) {
580
+ node $genPath sync-hosts @extraArgs
581
+ } elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
582
+ npx -y laporan-generator sync-hosts @extraArgs
583
+ } else {
584
+ Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
585
+ }
547
586
  } else {
548
587
  Write-Host "Node.js diperlukan untuk sinkronisasi multi-host AI agent." -ForegroundColor Yellow
549
588
  }
package/lib/init.js CHANGED
@@ -3,6 +3,8 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
+ const os = require('os');
7
+
6
8
  function copyRecursiveSync(src, dest) {
7
9
  if (!fs.existsSync(src)) return;
8
10
  const stats = fs.statSync(src);
@@ -18,25 +20,48 @@ function copyRecursiveSync(src, dest) {
18
20
  }
19
21
  }
20
22
 
21
- function run(argv = []) {
22
- const cwd = process.cwd();
23
- console.log('\n==========================================================');
24
- console.log(' LAPORAN GENERATOR - INISIALISASI PROYEK DOKUMEN ');
25
- console.log('==========================================================\n');
23
+ function initProject(options = {}) {
24
+ const targetDir = options.targetDir ? path.resolve(options.targetDir) : process.cwd();
25
+ const silent = !!options.silent;
26
+ const isTruthy = (val) => val === true || val === 'true';
27
+
28
+ if (!fs.existsSync(targetDir)) {
29
+ fs.mkdirSync(targetDir, { recursive: true });
30
+ }
31
+
32
+ const metaPath = path.join(targetDir, 'metadata.yml');
33
+ if (fs.existsSync(metaPath) && !options.force) {
34
+ if (!silent) {
35
+ console.log('[WARN] Dokumen laporan sudah ada di direktori ini (metadata.yml ditemukan).');
36
+ }
37
+ return {
38
+ success: false,
39
+ alreadyExists: true,
40
+ targetDir,
41
+ message: 'Dokumen laporan sudah ada di direktori ini (metadata.yml ditemukan).'
42
+ };
43
+ }
44
+
45
+ // Cari direktori resources (prioritas: skill resources, package root, global home)
46
+ const candidates = [
47
+ path.join(__dirname, '../.agents/skills/laporan-generator/resources'),
48
+ path.join(__dirname, '..'),
49
+ path.join(os.homedir(), '.agents/skills/laporan-generator/resources'),
50
+ path.join(os.homedir(), '.gemini/config/skills/laporan-generator/resources')
51
+ ];
26
52
 
27
- const metaPath = path.join(cwd, 'metadata.yml');
28
- if (fs.existsSync(metaPath)) {
29
- console.log('[WARN] Dokumen laporan sudah ada di direktori ini (metadata.yml ditemukan).');
30
- return;
53
+ let sourceDir = null;
54
+ for (const c of candidates) {
55
+ if (fs.existsSync(path.join(c, 'template.typ')) && fs.existsSync(path.join(c, 'metadata.yml'))) {
56
+ sourceDir = c;
57
+ break;
58
+ }
31
59
  }
32
60
 
33
- // Cari direktori resources (bisa dari package root atau global skills)
34
- const pkgResources = path.join(__dirname, '../.agents/skills/laporan-generator/resources');
35
- const globalResources = path.join(require('os').homedir(), '.agents/skills/laporan-generator/resources');
36
- const sourceDir = fs.existsSync(pkgResources) ? pkgResources : (fs.existsSync(globalResources) ? globalResources : null);
61
+ const copiedFiles = [];
37
62
 
38
63
  if (sourceDir) {
39
- console.log(`Menyalin template lengkap dari: ${sourceDir} ...\n`);
64
+ if (!silent) console.log(`Menyalin template lengkap dari: ${sourceDir} ...\n`);
40
65
  const filesToCopy = [
41
66
  'metadata.yml',
42
67
  'cover.md',
@@ -58,61 +83,130 @@ function run(argv = []) {
58
83
 
59
84
  for (const f of filesToCopy) {
60
85
  const srcFile = path.join(sourceDir, f);
61
- const destFile = path.join(cwd, f);
62
- if (fs.existsSync(srcFile) && !fs.existsSync(destFile)) {
86
+ const destFile = path.join(targetDir, f);
87
+ if (fs.existsSync(srcFile) && (!fs.existsSync(destFile) || options.force)) {
63
88
  copyRecursiveSync(srcFile, destFile);
64
- console.log(` ✔ Disalin: ${f}`);
89
+ copiedFiles.push(f);
90
+ if (!silent) console.log(` ✔ Disalin: ${f}`);
65
91
  }
66
92
  }
67
93
  } else {
68
94
  // Fallback manual jika source resources tidak ditemukan
69
- const defaultMeta = `title: "Laporan Akademik & Proyek"
95
+ const defaultMeta = `---
96
+ title: "${options.title || 'Laporan Akademik & Proyek'}"
97
+ subtitle: "Laporan Penelitian / Tugas Akhir"
70
98
  author:
71
- - name: "Nama Penulis"
72
- id: "1234567890"
73
- date: "2026"
74
- institution: "Universitas / Institusi Akademik"
75
- faculty: "Fakultas Teknik & Ilmu Komputer"
76
- department: "Program Studi Informatika"
77
- location: "Indonesia"
78
- margin_preset: "skripsi-4433"
79
- font_family: "Times New Roman"
80
- font_size: "12pt"
81
- line_spacing: "0.75em"
82
- first_line_indent: "1.25cm"
83
- heading_chapter_prefix: "BAB "
84
- heading_chapter_num_format: "roman"
99
+ - name: "${options.authorName || 'Nama Penulis'}"
100
+ nim: "${options.authorId || '1234567890'}"
101
+ date: "${options.year || '2026'}"
102
+ institution: "${options.institution || 'Universitas / Institusi Akademik'}"
103
+ faculty: "${options.faculty || 'Fakultas Teknik'}"
104
+ department: "${options.department || 'Program Studi Informatika'}"
105
+ year: "${options.year || '2026'}"
106
+ preset: "${options.preset || 'skripsi-4433'}"
107
+ daftar_gambar: ${isTruthy(options.daftar_gambar) ? 'true' : 'false'}
108
+ daftar_tabel: ${isTruthy(options.daftar_tabel) ? 'true' : 'false'}
109
+ ...
85
110
  `;
86
111
  fs.writeFileSync(metaPath, defaultMeta, 'utf8');
87
- console.log(' ✔ Dibuat: metadata.yml (Preset: skripsi-4433)');
112
+ copiedFiles.push('metadata.yml');
88
113
 
89
- const coverPath = path.join(cwd, 'cover.md');
114
+ const coverPath = path.join(targetDir, 'cover.md');
90
115
  fs.writeFileSync(coverPath, '# Laporan Akademik\n\nDokumen ini disusun sebagai dokumentasi resmi laporan proyek/penelitian.\n', 'utf8');
91
- console.log(' ✔ Dibuat: cover.md');
116
+ copiedFiles.push('cover.md');
92
117
 
93
- const chaptersDir = path.join(cwd, 'chapters');
118
+ const chaptersDir = path.join(targetDir, 'chapters');
94
119
  if (!fs.existsSync(chaptersDir)) fs.mkdirSync(chaptersDir, { recursive: true });
95
120
  const bab1 = `# Pendahuluan\n\n## Latar Belakang\nTuliskan latar belakang masalah di sini.\n\n## Rumusan Masalah\n1. Masalah pertama.\n2. Masalah kedua.\n\n## Tujuan Penelitian\nTujuan dari pelaksanaan proyek ini adalah menghasilkan analisis yang komprehensif.\n`;
96
121
  fs.writeFileSync(path.join(chaptersDir, 'bab1-pendahuluan.md'), bab1, 'utf8');
97
- console.log(' ✔ Dibuat: chapters/bab1-pendahuluan.md');
122
+ copiedFiles.push('chapters/bab1-pendahuluan.md');
98
123
 
99
- const refBibPath = path.join(cwd, 'references.bib');
124
+ const refBibPath = path.join(targetDir, 'references.bib');
100
125
  fs.writeFileSync(refBibPath, `@book{contoh2026,\n title = {Panduan Penulisan Karya Ilmiah},\n author = {Penulis, Contoh},\n year = {2026},\n publisher = {Penerbit Akademik}\n}\n`, 'utf8');
101
- console.log(' ✔ Dibuat: references.bib');
126
+ copiedFiles.push('references.bib');
127
+ }
128
+
129
+ // Update metadata.yml if custom options were provided
130
+ if (fs.existsSync(metaPath) && (options.title || options.preset || options.authorName || options.institution || options.daftar_gambar !== undefined || options.daftar_tabel !== undefined)) {
131
+ try {
132
+ let metaContent = fs.readFileSync(metaPath, 'utf8');
133
+ if (options.title) {
134
+ metaContent = metaContent.replace(/^title:\s*".*?"/m, `title: "${options.title.replace(/"/g, '\\"')}"`);
135
+ }
136
+ if (options.preset) {
137
+ metaContent = metaContent.replace(/^preset:\s*".*?"/m, `preset: "${options.preset}"`);
138
+ }
139
+ if (options.institution) {
140
+ metaContent = metaContent.replace(/^institution:\s*".*?"/m, `institution: "${options.institution.replace(/"/g, '\\"')}"`);
141
+ }
142
+ if (options.faculty) {
143
+ metaContent = metaContent.replace(/^faculty:\s*".*?"/m, `faculty: "${options.faculty.replace(/"/g, '\\"')}"`);
144
+ }
145
+ if (options.department) {
146
+ metaContent = metaContent.replace(/^department:\s*".*?"/m, `department: "${options.department.replace(/"/g, '\\"')}"`);
147
+ }
148
+ if (options.year) {
149
+ metaContent = metaContent.replace(/^year:\s*".*?"/m, `year: "${options.year}"`);
150
+ }
151
+ if (options.authorName) {
152
+ metaContent = metaContent.replace(/name:\s*".*?"/, `name: "${options.authorName.replace(/"/g, '\\"')}"`);
153
+ if (options.authorId) {
154
+ metaContent = metaContent.replace(/nim:\s*".*?"/, `nim: "${options.authorId}"`);
155
+ }
156
+ }
157
+ if (options.daftar_gambar !== undefined) {
158
+ const val = isTruthy(options.daftar_gambar) ? 'true' : 'false';
159
+ if (/^daftar_gambar:\s*(true|false)/m.test(metaContent)) {
160
+ metaContent = metaContent.replace(/^daftar_gambar:\s*(true|false)/m, `daftar_gambar: ${val}`);
161
+ } else {
162
+ metaContent = metaContent.replace(/preset:\s*".*?"/m, `daftar_gambar: ${val}\n$&`);
163
+ }
164
+ }
165
+ if (options.daftar_tabel !== undefined) {
166
+ const val = isTruthy(options.daftar_tabel) ? 'true' : 'false';
167
+ if (/^daftar_tabel:\s*(true|false)/m.test(metaContent)) {
168
+ metaContent = metaContent.replace(/^daftar_tabel:\s*(true|false)/m, `daftar_tabel: ${val}`);
169
+ } else {
170
+ metaContent = metaContent.replace(/preset:\s*".*?"/m, `daftar_tabel: ${val}\n$&`);
171
+ }
172
+ }
173
+ fs.writeFileSync(metaPath, metaContent, 'utf8');
174
+ } catch (err) {
175
+ console.warn(`[init] Failed to customize metadata.yml: ${err.message}`);
176
+ }
102
177
  }
103
178
 
104
179
  // Ensure scripts are executable on Unix
105
180
  try {
106
181
  if (process.platform !== 'win32') {
107
- const laporanBin = path.join(cwd, 'laporan');
108
- const buildSh = path.join(cwd, 'build.sh');
182
+ const laporanBin = path.join(targetDir, 'laporan');
183
+ const buildSh = path.join(targetDir, 'build.sh');
109
184
  if (fs.existsSync(laporanBin)) fs.chmodSync(laporanBin, '755');
110
185
  if (fs.existsSync(buildSh)) fs.chmodSync(buildSh, '755');
111
186
  }
112
- } catch (_) {}
187
+ } catch (err) {
188
+ console.warn(`[init] Failed to set executable permissions: ${err.message}`);
189
+ }
113
190
 
114
- console.log('\n✨ Inisialisasi dokumen selesai!');
115
- console.log('Jalankan "./laporan build" atau "npx laporan-generator build" untuk mengompilasi dokumen ke PDF & Word!\n');
191
+ return {
192
+ success: true,
193
+ targetDir,
194
+ copiedFiles,
195
+ message: 'Inisialisasi dokumen selesai dengan sukses.'
196
+ };
197
+ }
198
+
199
+ function run(argv = []) {
200
+ const cwd = process.cwd();
201
+ console.log('\n==========================================================');
202
+ console.log(' LAPORAN GENERATOR - INISIALISASI PROYEK DOKUMEN ');
203
+ console.log('==========================================================\n');
204
+
205
+ const res = initProject({ targetDir: cwd });
206
+ if (res.success) {
207
+ console.log('\n✨ Inisialisasi dokumen selesai!');
208
+ console.log('Jalankan "./laporan build" atau "npx laporan-generator build" untuk mengompilasi dokumen ke PDF & Word!\n');
209
+ }
116
210
  }
117
211
 
118
- module.exports = { run };
212
+ module.exports = { run, initProject };