laporan-generator 2.6.2 → 2.7.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/lib/sync-hosts.js CHANGED
@@ -1,9 +1,9 @@
1
- 'use strict';
1
+ "use strict";
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
- const { removeRecursiveSync, parseJsonc } = require('./uninstall');
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const os = require("os");
6
+ const { removeRecursiveSync, parseJsonc } = require("./uninstall");
7
7
 
8
8
  function copyRecursiveSync(src, dest) {
9
9
  if (!fs.existsSync(src)) return;
@@ -20,28 +20,154 @@ function copyRecursiveSync(src, dest) {
20
20
  }
21
21
  }
22
22
 
23
+ function syncInternalResources(packageRoot) {
24
+ const skillRoot = path.join(packageRoot, ".agents/skills/laporan-generator");
25
+ const resDir = path.join(skillRoot, "resources");
26
+ const skillScriptsDir = path.join(skillRoot, "scripts");
27
+ const resScriptsDir = path.join(resDir, "scripts");
28
+ const resPresetsDir = path.join(resDir, "presets");
29
+
30
+ fs.mkdirSync(resDir, { recursive: true });
31
+ fs.mkdirSync(skillScriptsDir, { recursive: true });
32
+ fs.mkdirSync(resScriptsDir, { recursive: true });
33
+ fs.mkdirSync(resPresetsDir, { recursive: true });
34
+
35
+ // 1. Sync scripts from root scripts/
36
+ const srcScripts = path.join(packageRoot, "scripts");
37
+ if (fs.existsSync(srcScripts)) {
38
+ for (const f of fs.readdirSync(srcScripts)) {
39
+ if (f.startsWith("__pycache__") || f.endsWith(".pyc")) continue;
40
+ const srcFile = path.join(srcScripts, f);
41
+ copyRecursiveSync(srcFile, path.join(skillScriptsDir, f));
42
+ copyRecursiveSync(srcFile, path.join(resScriptsDir, f));
43
+ }
44
+ }
45
+
46
+ // 2. Sync presets from root presets/
47
+ const srcPresets = path.join(packageRoot, "presets");
48
+ if (fs.existsSync(srcPresets)) {
49
+ copyRecursiveSync(srcPresets, resPresetsDir);
50
+ }
51
+
52
+ // 3. Sync template files
53
+ const templateFiles = [
54
+ "template.typ",
55
+ "reference.docx",
56
+ "docx.lua",
57
+ "references.bib",
58
+ "apa.csl",
59
+ "metadata.yml",
60
+ "cover.md",
61
+ "logo.jpg",
62
+ "Makefile",
63
+ "build.sh",
64
+ "build-docx.sh",
65
+ "laporan",
66
+ "laporan.ps1"
67
+ ];
68
+
69
+ for (const tf of templateFiles) {
70
+ const src = path.join(packageRoot, tf);
71
+ if (fs.existsSync(src)) {
72
+ fs.copyFileSync(src, path.join(resDir, tf));
73
+ }
74
+ }
75
+ }
76
+
77
+ function registerMcpServers(packageRoot, home) {
78
+ const mcpBin = path.join(packageRoot, "bin/mcp-server.js");
79
+
80
+ // 1. Antigravity MCP (~/.gemini/config/mcp_config.json)
81
+ const agyMcpPath = path.join(home, ".gemini/config/mcp_config.json");
82
+ try {
83
+ let agyConfig = { mcpServers: {} };
84
+ if (fs.existsSync(agyMcpPath)) {
85
+ try {
86
+ agyConfig = JSON.parse(fs.readFileSync(agyMcpPath, "utf8"));
87
+ } catch (_) {
88
+ agyConfig = { mcpServers: {} };
89
+ }
90
+ }
91
+ if (!agyConfig.mcpServers) agyConfig.mcpServers = {};
92
+ agyConfig.mcpServers["laporan"] = {
93
+ command: "node",
94
+ args: [mcpBin],
95
+ disabled: false
96
+ };
97
+ fs.mkdirSync(path.dirname(agyMcpPath), { recursive: true });
98
+ fs.writeFileSync(agyMcpPath, JSON.stringify(agyConfig, null, 2), "utf8");
99
+ console.log(" ✔ [ANTIGRAVITY MCP] Registered server \"laporan\" -> ~/.gemini/config/mcp_config.json");
100
+ } catch (err) {
101
+ console.log(" ✖ [ANTIGRAVITY MCP SKIP] " + err.message);
102
+ }
103
+
104
+ // 2. Claude Desktop & Claude Code (~/.config/Claude/claude_desktop_config.json, ~/.claude.json)
105
+ const claudePaths = [
106
+ path.join(home, ".config/Claude/claude_desktop_config.json"),
107
+ path.join(home, ".claude.json")
108
+ ];
109
+ for (const cPath of claudePaths) {
110
+ try {
111
+ let conf = { mcpServers: {} };
112
+ if (fs.existsSync(cPath)) {
113
+ try {
114
+ conf = JSON.parse(fs.readFileSync(cPath, "utf8"));
115
+ } catch (_) {}
116
+ }
117
+ if (!conf.mcpServers) conf.mcpServers = {};
118
+ conf.mcpServers["laporan"] = {
119
+ command: "node",
120
+ args: [mcpBin]
121
+ };
122
+ fs.mkdirSync(path.dirname(cPath), { recursive: true });
123
+ fs.writeFileSync(cPath, JSON.stringify(conf, null, 2), "utf8");
124
+ console.log(" ✔ [CLAUDE MCP] Registered server \"laporan\" -> " + cPath);
125
+ } catch (_) {}
126
+ }
127
+
128
+ // 3. OpenCode (~/.config/opencode/opencode.jsonc)
129
+ const opencodeConfigPath = path.join(home, ".config/opencode/opencode.jsonc");
130
+ if (fs.existsSync(opencodeConfigPath)) {
131
+ try {
132
+ const raw = fs.readFileSync(opencodeConfigPath, "utf8");
133
+ const opencodeConfig = parseJsonc(raw);
134
+ if (!opencodeConfig.mcpServers) opencodeConfig.mcpServers = {};
135
+ opencodeConfig.mcpServers["laporan"] = {
136
+ command: "node",
137
+ args: [mcpBin]
138
+ };
139
+ fs.writeFileSync(opencodeConfigPath, JSON.stringify(opencodeConfig, null, 2), "utf8");
140
+ console.log(" ✔ [OPENCODE MCP] Registered MCP server \"laporan\" -> ~/.config/opencode/opencode.jsonc");
141
+ } catch (_) {}
142
+ }
143
+ }
144
+
23
145
  function run(argv = []) {
24
146
  const home = os.homedir();
25
- const packageRoot = path.join(__dirname, '..');
26
- const skillGeneratorSource = path.join(packageRoot, '.agents/skills/laporan-generator');
27
- const workflowSource = path.join(packageRoot, '.agents/workflows/laporan-generator.md');
147
+ const packageRoot = path.join(__dirname, "..");
148
+ const skillGeneratorSource = path.join(packageRoot, ".agents/skills/laporan-generator");
149
+ const workflowSource = path.join(packageRoot, ".agents/workflows/laporan-generator.md");
28
150
 
29
- console.log('\n==========================================================');
30
- console.log(' LAPORAN GENERATOR - AI AGENT SKILLS SYNCHRONIZER ');
31
- console.log('==========================================================\n');
151
+ console.log("\n==========================================================");
152
+ console.log(" LAPORAN GENERATOR - AI AGENT SKILLS & MCP SYNC ");
153
+ console.log("==========================================================\n");
32
154
 
33
- // Bersihkan alias lama/duplikat 'laporan' agar tidak dobel dengan 'laporan-generator'
155
+ // Step 0: Sinkronisasi berkas internal sebelum distribusi
156
+ syncInternalResources(packageRoot);
157
+ console.log(" ✔ [INTERNAL SYNC] Root scripts & templates synced to .agents/skills/laporan-generator");
158
+
159
+ // Bersihkan alias lama/duplikat laporan agar tidak dobel dengan laporan-generator
34
160
  const legacyDuplicates = [
35
- path.join(home, '.agents/skills/laporan'),
36
- path.join(home, '.gemini/config/skills/laporan'),
37
- path.join(home, '.claude/skills/laporan'),
38
- path.join(home, '.grok/skills/laporan'),
39
- path.join(home, '.config/opencode/skills/laporan'),
40
- path.join(process.cwd(), '.agents/skills/laporan'),
41
- path.join(home, '.kiro/agents/laporan.json'),
42
- path.join(home, '.agents/workflows/laporan.md'),
43
- path.join(process.cwd(), '.agents/workflows/laporan.md'),
44
- path.join(home, '.claude/commands/laporan.md')
161
+ path.join(home, ".agents/skills/laporan"),
162
+ path.join(home, ".gemini/config/skills/laporan"),
163
+ path.join(home, ".claude/skills/laporan"),
164
+ path.join(home, ".grok/skills/laporan"),
165
+ path.join(home, ".config/opencode/skills/laporan"),
166
+ path.join(process.cwd(), ".agents/skills/laporan"),
167
+ path.join(home, ".kiro/agents/laporan.json"),
168
+ path.join(home, ".agents/workflows/laporan.md"),
169
+ path.join(process.cwd(), ".agents/workflows/laporan.md"),
170
+ path.join(home, ".claude/commands/laporan.md")
45
171
  ];
46
172
  for (const legacy of legacyDuplicates) {
47
173
  if (fs.existsSync(legacy)) {
@@ -51,28 +177,28 @@ function run(argv = []) {
51
177
 
52
178
  const targets = [
53
179
  {
54
- name: 'Global Agents (~/.agents/skills/)',
55
- skillsDir: path.join(home, '.agents/skills')
180
+ name: "Global Agents (~/.agents/skills/)",
181
+ skillsDir: path.join(home, ".agents/skills")
56
182
  },
57
183
  {
58
- name: 'Antigravity CLI (~/.gemini/config/skills/)',
59
- skillsDir: path.join(home, '.gemini/config/skills')
184
+ name: "Antigravity CLI (~/.gemini/config/skills/)",
185
+ skillsDir: path.join(home, ".gemini/config/skills")
60
186
  },
61
187
  {
62
- name: 'Claude Code (~/.claude/skills/)',
63
- skillsDir: path.join(home, '.claude/skills')
188
+ name: "Claude Code (~/.claude/skills/)",
189
+ skillsDir: path.join(home, ".claude/skills")
64
190
  },
65
191
  {
66
- name: 'Grok CLI (~/.grok/skills/)',
67
- skillsDir: path.join(home, '.grok/skills')
192
+ name: "Grok CLI (~/.grok/skills/)",
193
+ skillsDir: path.join(home, ".grok/skills")
68
194
  },
69
195
  {
70
- name: 'OpenCode (~/.config/opencode/skills/)',
71
- skillsDir: path.join(home, '.config/opencode/skills')
196
+ name: "OpenCode (~/.config/opencode/skills/)",
197
+ skillsDir: path.join(home, ".config/opencode/skills")
72
198
  },
73
199
  {
74
- name: 'Local Project Workspace (.agents/skills/)',
75
- skillsDir: path.join(process.cwd(), '.agents/skills')
200
+ name: "Local Project Workspace (.agents/skills/)",
201
+ skillsDir: path.join(process.cwd(), ".agents/skills")
76
202
  }
77
203
  ];
78
204
 
@@ -80,7 +206,7 @@ function run(argv = []) {
80
206
 
81
207
  for (const target of targets) {
82
208
  try {
83
- const destGen = path.join(target.skillsDir, 'laporan-generator');
209
+ const destGen = path.join(target.skillsDir, "laporan-generator");
84
210
  copyRecursiveSync(skillGeneratorSource, destGen);
85
211
  console.log(` ✔ [INSTALLED] ${target.name} (with full templates & resources)`);
86
212
  installedCount++;
@@ -90,25 +216,24 @@ function run(argv = []) {
90
216
  }
91
217
 
92
218
  // Daftarkan Slash Command ke OpenCode (~/.config/opencode/opencode.jsonc)
93
- const opencodeConfigPath = path.join(home, '.config/opencode/opencode.jsonc');
219
+ const opencodeConfigPath = path.join(home, ".config/opencode/opencode.jsonc");
94
220
  try {
95
221
  let opencodeConfig = {};
96
222
  if (fs.existsSync(opencodeConfigPath)) {
97
- const raw = fs.readFileSync(opencodeConfigPath, 'utf8');
223
+ const raw = fs.readFileSync(opencodeConfigPath, "utf8");
98
224
  try {
99
225
  opencodeConfig = parseJsonc(raw);
100
226
  } catch (_) {
101
227
  opencodeConfig = {};
102
228
  }
103
229
  }
104
- opencodeConfig['$schema'] = 'https://opencode.ai/config.json';
230
+ opencodeConfig["$schema"] = "https://opencode.ai/config.json";
105
231
  if (!opencodeConfig.command) {
106
232
  opencodeConfig.command = {};
107
233
  }
108
- // Hapus command 'laporan' jika ada duplikat sebelumnya
109
- delete opencodeConfig.command['laporan'];
234
+ delete opencodeConfig.command["laporan"];
110
235
 
111
- opencodeConfig.command['laporan-generator'] = {
236
+ opencodeConfig.command["laporan-generator"] = {
112
237
  description: "Universal Academic Report & Thesis Generator",
113
238
  template: "Gunakan skill laporan-generator untuk membuat atau mengompilasi dokumen laporan/skripsi akademik: $ARGUMENTS"
114
239
  };
@@ -117,62 +242,62 @@ function run(argv = []) {
117
242
  opencodeConfig.skills = {};
118
243
  }
119
244
  const skillPaths = new Set(opencodeConfig.skills.paths || []);
120
- skillPaths.add(path.join(home, '.agents/skills'));
121
- skillPaths.add(path.join(home, '.config/opencode/skills'));
245
+ skillPaths.add(path.join(home, ".agents/skills"));
246
+ skillPaths.add(path.join(home, ".config/opencode/skills"));
122
247
  opencodeConfig.skills.paths = Array.from(skillPaths);
123
248
 
124
249
  const opencodeDir = path.dirname(opencodeConfigPath);
125
250
  if (!fs.existsSync(opencodeDir)) {
126
251
  fs.mkdirSync(opencodeDir, { recursive: true });
127
252
  }
128
- fs.writeFileSync(opencodeConfigPath, JSON.stringify(opencodeConfig, null, 2), 'utf8');
129
- console.log(` ✔ [OPENCODE] Registered /laporan-generator command -> ~/.config/opencode/opencode.jsonc`);
253
+ fs.writeFileSync(opencodeConfigPath, JSON.stringify(opencodeConfig, null, 2), "utf8");
254
+ console.log(" ✔ [OPENCODE] Registered /laporan-generator command -> ~/.config/opencode/opencode.jsonc");
130
255
  } catch (err) {
131
- console.log(` ✖ [OPENCODE SKIP] ${err.message}`);
256
+ console.log(" ✖ [OPENCODE SKIP] " + err.message);
132
257
  }
133
258
 
134
259
  // Daftarkan Custom Agent ke Kiro CLI (~/.kiro/agents/laporan-generator.json)
135
- const kiroAgentsDir = path.join(home, '.kiro/agents');
260
+ const kiroAgentsDir = path.join(home, ".kiro/agents");
136
261
  try {
137
262
  if (!fs.existsSync(kiroAgentsDir)) {
138
263
  fs.mkdirSync(kiroAgentsDir, { recursive: true });
139
264
  }
140
265
  const kiroPrompt = [
141
- "You are an expert academic writing assistant powered by Laporan Generator.",
266
+ "You are an expert academic writing assistant powered by Laporan Generator and Native MCP Tools.",
142
267
  "",
143
268
  "CRITICAL INSTRUCTION: DO NOT write raw Typst code from scratch or create standalone .typ files (such as laporan.typ). DO NOT import @preview/ieee or external packages.",
144
269
  "",
145
270
  "Always follow the Modular Markdown Architecture:",
146
- "1. Scaffolding: If the workspace lacks templates, copy all files from ~/.agents/skills/laporan-generator/resources/* into the project directory (or run: npx laporan-generator init).",
147
- "2. Metadata: Edit metadata.yml to set title, author, institution, faculty, department, year, and campus preset (standard, skripsi-4433, itb-ta, ui-skripsi, ugm-skripsi, its-skripsi).",
271
+ "1. Scaffolding: Use MCP tool laporan_init or run npx laporan-generator init.",
272
+ "2. Metadata: Edit metadata.yml to set title, author, institution, faculty, department, year, and campus preset (standard, skripsi-4433, itb-ta, ui-skripsi, ugm-skripsi, its-skripsi, unpad-skripsi).",
148
273
  "3. Content: Write chapters in pure standard Markdown inside chapters/ (bab1-pendahuluan.md, bab2-tinjauan-pustaka.md, bab3-metodologi.md, bab4-hasil-dan-pembahasan.md, bab5-penutup.md).",
149
- "4. Citations: Add BibTeX entries to references.bib and cite in Markdown using [@citekey].",
150
- "5. Compilation: Run './laporan build' (or 'bash build.sh && make docx') to compile both PDF and Word DOCX simultaneously.",
151
- "6. Audit: Run './laporan doctor' to audit document health."
274
+ "4. Citations: Use MCP tool laporan_citations to search DOIs on Crossref, add BibTeX entries to references.bib, and cite in Markdown using [@citekey].",
275
+ "5. Audit: Use MCP tool laporan_doctor to audit document health.",
276
+ "6. Compilation: Use MCP tool laporan_build or run ./laporan build to compile both PDF and Word DOCX simultaneously."
152
277
  ].join("\n");
153
278
 
154
279
  const kiroConfig = {
155
280
  name: "laporan-generator",
156
- description: "Universal Academic Report & Thesis Generator using bundled Typst & DOCX templates",
281
+ description: "Universal Academic Report & Thesis Generator with Native MCP Tools",
157
282
  prompt: kiroPrompt,
158
283
  tools: ["read", "write", "shell", "grep", "glob"],
159
284
  allowedTools: ["read", "write", "shell", "grep", "glob"]
160
285
  };
161
286
  fs.writeFileSync(
162
- path.join(kiroAgentsDir, 'laporan-generator.json'),
287
+ path.join(kiroAgentsDir, "laporan-generator.json"),
163
288
  JSON.stringify(kiroConfig, null, 2),
164
- 'utf8'
289
+ "utf8"
165
290
  );
166
- console.log(` ✔ [KIRO AGENT] Registered agent: "laporan-generator" -> ~/.kiro/agents/`);
291
+ console.log(" ✔ [KIRO AGENT] Registered agent: \"laporan-generator\" -> ~/.kiro/agents/");
167
292
  } catch (err) {
168
- console.log(` ✖ [KIRO SKIP] ${err.message}`);
293
+ console.log(" ✖ [KIRO SKIP] " + err.message);
169
294
  }
170
295
 
171
- // Daftarkan Slash Commands / Workflows (Hanya satu /laporan-generator)
296
+ // Daftarkan Slash Commands / Workflows
172
297
  const workflowDestinations = [
173
- path.join(home, '.agents/workflows'),
174
- path.join(process.cwd(), '.agents/workflows'),
175
- path.join(home, '.claude/commands')
298
+ path.join(home, ".agents/workflows"),
299
+ path.join(process.cwd(), ".agents/workflows"),
300
+ path.join(home, ".claude/commands")
176
301
  ];
177
302
 
178
303
  for (const wDir of workflowDestinations) {
@@ -180,15 +305,18 @@ function run(argv = []) {
180
305
  if (!fs.existsSync(wDir)) {
181
306
  fs.mkdirSync(wDir, { recursive: true });
182
307
  }
183
- fs.copyFileSync(workflowSource, path.join(wDir, 'laporan-generator.md'));
308
+ fs.copyFileSync(workflowSource, path.join(wDir, "laporan-generator.md"));
184
309
  console.log(` ✔ [SLASH COMMAND] /laporan-generator -> ${wDir}`);
185
310
  } catch (_) {}
186
311
  }
187
312
 
188
- console.log('\n==========================================================');
189
- console.log(`✨ Sukses! Skill dan template /laporan-generator berhasil disinkronkan.`);
190
- console.log('Seluruh AI Agent (OpenCode, Kiro, Antigravity, Claude) kini siap digunakan!');
191
- console.log('==========================================================\n');
313
+ // Registrasi Native MCP Server
314
+ registerMcpServers(packageRoot, home);
315
+
316
+ console.log("\n==========================================================");
317
+ console.log("✨ Sukses! Skill dan MCP server /laporan-generator berhasil disinkronkan.");
318
+ console.log("Seluruh AI Agent (Antigravity, Claude, OpenCode, Kiro) kini siap digunakan!");
319
+ console.log("==========================================================\n");
192
320
  }
193
321
 
194
- module.exports = { run };
322
+ module.exports = { run, syncInternalResources };
package/lib/uninstall.js CHANGED
@@ -115,6 +115,27 @@ function run(argv = []) {
115
115
  }
116
116
  }
117
117
 
118
+ // 5. Bersihkan registrasi MCP Server
119
+ const mcpConfigs = [
120
+ path.join(home, '.gemini/config/mcp_config.json'),
121
+ path.join(home, '.claude.json'),
122
+ path.join(home, '.config/Claude/claude_desktop_config.json')
123
+ ];
124
+ for (const mcpFile of mcpConfigs) {
125
+ if (fs.existsSync(mcpFile)) {
126
+ try {
127
+ const raw = fs.readFileSync(mcpFile, 'utf8');
128
+ const data = JSON.parse(raw);
129
+ if (data.mcpServers && (data.mcpServers['laporan'] || data.mcpServers['laporan-generator'])) {
130
+ delete data.mcpServers['laporan'];
131
+ delete data.mcpServers['laporan-generator'];
132
+ fs.writeFileSync(mcpFile, JSON.stringify(data, null, 2), 'utf8');
133
+ console.log(` ✔ [CLEANED MCP] Removed laporan server from: ${mcpFile}`);
134
+ }
135
+ } catch (_) {}
136
+ }
137
+ }
138
+
118
139
  console.log('\n==========================================================');
119
140
  console.log('✨ Uninstall selesai! Seluruh integrasi AI agent telah dibersihkan.');
120
141
  console.log('==========================================================\n');
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "laporan-generator",
3
- "version": "2.6.2",
4
- "description": "Universal Academic Report & Thesis Generator with Multi-Host AI Agent Skills (Antigravity, Claude, Grok)",
3
+ "version": "2.7.0",
4
+ "description": "Universal Academic Report & Thesis Generator with Multi-Host AI Agent Skills & Native MCP Server",
5
5
  "main": "bin/laporan-generator.js",
6
6
  "bin": {
7
7
  "laporan-generator": "bin/laporan-generator.js",
8
- "laporan": "bin/laporan-generator.js"
8
+ "laporan": "bin/laporan-generator.js",
9
+ "laporan-mcp": "bin/mcp-server.js"
9
10
  },
10
11
  "files": [
11
12
  "bin/",
@@ -35,7 +36,10 @@
35
36
  ],
36
37
  "scripts": {
37
38
  "test": "./test.sh",
39
+ "test:mcp": "node tests/test_mcp.js",
40
+ "mcp": "node bin/mcp-server.js",
38
41
  "doctor": "node bin/laporan-generator.js doctor",
42
+ "stats": "node bin/laporan-generator.js stats",
39
43
  "sync-hosts": "node bin/laporan-generator.js sync-hosts",
40
44
  "setup": "node bin/laporan-generator.js setup"
41
45
  },
@@ -46,6 +50,8 @@
46
50
  "thesis-generator",
47
51
  "skripsi",
48
52
  "ai-agents",
53
+ "mcp",
54
+ "model-context-protocol",
49
55
  "antigravity",
50
56
  "antigravity-cli",
51
57
  "claude-code",
package/scripts/bundle.py CHANGED
@@ -15,8 +15,17 @@ NC = "\033[0m"
15
15
 
16
16
 
17
17
  def main():
18
- root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
- os.chdir(root)
18
+ pos_args = [a for a in sys.argv[1:] if not a.startswith("-")]
19
+
20
+ target_dir = os.getcwd()
21
+ if pos_args:
22
+ target_dir = os.path.abspath(pos_args[0])
23
+ elif not (os.path.isfile(os.path.join(target_dir, "metadata.yml")) or os.path.isdir(os.path.join(target_dir, "chapters"))):
24
+ pkg_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25
+ if os.path.isfile(os.path.join(pkg_root, "metadata.yml")):
26
+ target_dir = pkg_root
27
+
28
+ os.chdir(target_dir)
20
29
 
21
30
  print(f"{CYAN}{BOLD}")
22
31
  print(" ========================================================")