optikit 1.3.0 → 1.4.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.
@@ -24,5 +24,8 @@
24
24
  "WebSearch",
25
25
  "Bash(git add:*)"
26
26
  ]
27
- }
27
+ },
28
+ "enabledMcpjsonServers": [
29
+ "optikit"
30
+ ]
28
31
  }
package/CHANGELOG.md CHANGED
@@ -12,6 +12,14 @@ We follow **Semantic Versioning (SemVer)**:
12
12
 
13
13
  ---
14
14
 
15
+ ### 🌟 [1.4.0] - Interactive Repo Picker & Module Cleanup
16
+
17
+ - 📂 Interactive arrow-key directory picker for `gen repo` — browse, open, go back, or create folders
18
+ - 🔄 Removed repo from `generate module` — lighter module scaffolding
19
+ - 🏗️ Repo is now standalone (own import, no `part of`) and can live anywhere
20
+
21
+ ---
22
+
15
23
  ### 🌟 [1.3.0] - MCP Plugin, Short Aliases & Combo Flags
16
24
 
17
25
  - 🤖 **Claude Code Integration**:
@@ -29,7 +29,7 @@ export const projectCommands = [
29
29
  builder: (yargs) => {
30
30
  return yargs.positional("moduleName", { describe: "The module name", type: "string" });
31
31
  },
32
- handler: (argv) => { generateRepoModule(argv.moduleName); },
32
+ handler: async (argv) => { await generateRepoModule(argv.moduleName); },
33
33
  },
34
34
  {
35
35
  command: "add-route <moduleName>",
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import chalk from "chalk";
3
4
  import { createDirectories, writeFile, getClassName, } from "../../utils/helpers/file.js";
4
5
  import { LoggerHelpers } from "../../utils/services/logger.js";
5
6
  import { handleCommandError } from "../../utils/helpers/error.js";
@@ -31,7 +32,6 @@ function generateModule(moduleName) {
31
32
  generateScreen(moduleName, path.join(modulePath, "screen"));
32
33
  generateImport(moduleName, path.join(modulePath, "import"));
33
34
  generateStateFactory(moduleName, path.join(modulePath, "factory"));
34
- generateRepo(moduleName, path.join(modulePath, "repo"));
35
35
  LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
36
36
  }
37
37
  catch (error) {
@@ -136,7 +136,6 @@ part '../event/${moduleName}_event.dart';
136
136
  part '../screen/${moduleName}_screen.dart';
137
137
  part '../state/${moduleName}_state.dart';
138
138
  part '../factory/${moduleName}_factory.dart';
139
- part '../repo/${moduleName}_repo.dart';
140
139
  `;
141
140
  writeFile(importFilePath, template);
142
141
  LoggerHelpers.success(`Import file ${moduleName}_import.dart created in ${importPath}`);
@@ -159,7 +158,7 @@ class ${className}Factory extends BaseFactory {
159
158
  function generateRepo(moduleName, repoPath) {
160
159
  const repoFilePath = path.join(repoPath, `${moduleName}_repo.dart`);
161
160
  const className = getClassName(moduleName);
162
- const template = `part of '../import/${moduleName}_import.dart';
161
+ const template = `import 'package:opticore/opticore.dart';
163
162
 
164
163
  class ${className}Repo extends BaseRepo {
165
164
 
@@ -170,17 +169,213 @@ class ${className}Repo extends BaseRepo {
170
169
  );
171
170
  }
172
171
  }
173
- `;
172
+ `;
174
173
  writeFile(repoFilePath, template);
175
174
  LoggerHelpers.success(`Repo file ${moduleName}_repo.dart created in ${repoPath}`);
176
175
  }
176
+ function buildPickerItems(currentPath, rootPath) {
177
+ const items = [];
178
+ // "Select here" always first
179
+ items.push({ label: "Select this directory", icon: "✔", action: "select" });
180
+ // Go back (unless at root)
181
+ const canGoBack = currentPath !== rootPath && currentPath !== ".";
182
+ if (canGoBack) {
183
+ items.push({ label: "..", icon: "↩", action: "back" });
184
+ }
185
+ // Subdirectories
186
+ if (fs.existsSync(currentPath)) {
187
+ const dirs = fs.readdirSync(currentPath, { withFileTypes: true })
188
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
189
+ .map(e => e.name)
190
+ .sort();
191
+ for (const dir of dirs) {
192
+ items.push({ label: `${dir}/`, icon: "📁", action: "open", dirName: dir });
193
+ }
194
+ }
195
+ // Create new folder always last
196
+ items.push({ label: "Create new folder", icon: "✚", action: "create" });
197
+ return items;
198
+ }
177
199
  /**
178
- * Generates a repository for an existing module.
179
- * Creates repo directory, generates repo file, and appends part directive to import file.
200
+ * Interactive arrow-key directory picker.
201
+ * Navigate with arrow keys, enter to select/open, esc to cancel.
180
202
  */
181
- function generateRepoModule(moduleName) {
203
+ function pickDirectory(startPath) {
204
+ return new Promise((resolve, reject) => {
205
+ let currentPath = startPath;
206
+ const rootPath = startPath;
207
+ let cursor = 0;
208
+ let items = buildPickerItems(currentPath, rootPath);
209
+ let totalLines = 0;
210
+ let creatingFolder = false;
211
+ let folderInput = "";
212
+ const stdin = process.stdin;
213
+ const wasRaw = stdin.isRaw ?? false;
214
+ stdin.setRawMode(true);
215
+ stdin.resume();
216
+ stdin.setEncoding("utf8");
217
+ function render() {
218
+ // Clear previous output
219
+ if (totalLines > 0) {
220
+ process.stdout.write(`\x1B[${totalLines}A`);
221
+ process.stdout.write("\x1B[0J");
222
+ }
223
+ const lines = [];
224
+ // Header — current path with breadcrumb
225
+ lines.push("");
226
+ lines.push(chalk.cyan.bold(` 📂 ${currentPath}/`));
227
+ lines.push(chalk.gray(" ─────────────────────────────────────"));
228
+ // Menu items
229
+ for (let i = 0; i < items.length; i++) {
230
+ const item = items[i];
231
+ const isActive = i === cursor;
232
+ if (isActive) {
233
+ const pointer = chalk.cyan.bold("❯");
234
+ const icon = item.action === "select" ? chalk.green(item.icon) :
235
+ item.action === "back" ? chalk.yellow(item.icon) :
236
+ item.action === "create" ? chalk.magenta(item.icon) :
237
+ chalk.blue(item.icon);
238
+ lines.push(` ${pointer} ${icon} ${chalk.white.bold(item.label)}`);
239
+ }
240
+ else {
241
+ const icon = chalk.gray(item.icon);
242
+ lines.push(` ${icon} ${chalk.gray(item.label)}`);
243
+ }
244
+ }
245
+ lines.push(chalk.gray(" ─────────────────────────────────────"));
246
+ if (creatingFolder) {
247
+ lines.push(chalk.magenta(` ✚ New folder name: `) + chalk.white.bold(folderInput) + chalk.gray("▌"));
248
+ }
249
+ else {
250
+ lines.push(chalk.gray(" ↑↓ navigate ⏎ select/open ← back esc cancel"));
251
+ }
252
+ const output = lines.join("\n") + "\n";
253
+ process.stdout.write(output);
254
+ totalLines = lines.length;
255
+ }
256
+ function cleanup() {
257
+ stdin.setRawMode(wasRaw);
258
+ stdin.removeListener("data", onKeypress);
259
+ stdin.pause();
260
+ }
261
+ function refreshItems() {
262
+ items = buildPickerItems(currentPath, rootPath);
263
+ cursor = 0;
264
+ }
265
+ function onKeypress(key) {
266
+ // Handle folder creation mode
267
+ if (creatingFolder) {
268
+ if (key === "\r") {
269
+ // Enter — create the folder
270
+ const trimmed = folderInput.trim();
271
+ if (trimmed.length > 0) {
272
+ const newPath = path.join(currentPath, trimmed);
273
+ fs.mkdirSync(newPath, { recursive: true });
274
+ currentPath = newPath;
275
+ refreshItems();
276
+ }
277
+ creatingFolder = false;
278
+ folderInput = "";
279
+ render();
280
+ }
281
+ else if (key === "\x1B") {
282
+ // Esc — cancel folder creation
283
+ creatingFolder = false;
284
+ folderInput = "";
285
+ render();
286
+ }
287
+ else if (key === "\x7F" || key === "\b") {
288
+ // Backspace
289
+ folderInput = folderInput.slice(0, -1);
290
+ render();
291
+ }
292
+ else if (key.length === 1 && key.charCodeAt(0) >= 32) {
293
+ // Regular character
294
+ folderInput += key;
295
+ render();
296
+ }
297
+ return;
298
+ }
299
+ const item = items[cursor];
300
+ // Escape — cancel (single \x1B only, not arrow key sequences)
301
+ if (key === "\x1B" && key.length === 1) {
302
+ cleanup();
303
+ reject(new Error("Directory selection cancelled."));
304
+ return;
305
+ }
306
+ // Arrow keys
307
+ if (key === "\x1B[A") {
308
+ // Up
309
+ cursor = cursor > 0 ? cursor - 1 : items.length - 1;
310
+ render();
311
+ return;
312
+ }
313
+ if (key === "\x1B[B") {
314
+ // Down
315
+ cursor = cursor < items.length - 1 ? cursor + 1 : 0;
316
+ render();
317
+ return;
318
+ }
319
+ if (key === "\x1B[D") {
320
+ // Left arrow — go back
321
+ if (currentPath !== rootPath && currentPath !== ".") {
322
+ currentPath = path.dirname(currentPath);
323
+ refreshItems();
324
+ render();
325
+ }
326
+ return;
327
+ }
328
+ if (key === "\x1B[C") {
329
+ // Right arrow — open directory if cursor is on a folder
330
+ if (item.action === "open" && item.dirName) {
331
+ currentPath = path.join(currentPath, item.dirName);
332
+ refreshItems();
333
+ render();
334
+ }
335
+ return;
336
+ }
337
+ // Enter — perform action
338
+ if (key === "\r") {
339
+ if (item.action === "select") {
340
+ cleanup();
341
+ resolve(currentPath);
342
+ return;
343
+ }
344
+ if (item.action === "back") {
345
+ currentPath = path.dirname(currentPath);
346
+ refreshItems();
347
+ render();
348
+ return;
349
+ }
350
+ if (item.action === "open" && item.dirName) {
351
+ currentPath = path.join(currentPath, item.dirName);
352
+ refreshItems();
353
+ render();
354
+ return;
355
+ }
356
+ if (item.action === "create") {
357
+ creatingFolder = true;
358
+ folderInput = "";
359
+ render();
360
+ return;
361
+ }
362
+ }
363
+ // Ctrl+C
364
+ if (key === "\x03") {
365
+ cleanup();
366
+ process.exit(0);
367
+ }
368
+ }
369
+ stdin.on("data", onKeypress);
370
+ render();
371
+ });
372
+ }
373
+ /**
374
+ * Generates a repository file at a user-selected location.
375
+ * Opens an interactive directory picker starting from lib/.
376
+ */
377
+ async function generateRepoModule(moduleName) {
182
378
  try {
183
- // Validate module name
184
379
  if (!moduleName || moduleName.trim().length === 0) {
185
380
  LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_EMPTY);
186
381
  process.exit(1);
@@ -189,37 +384,15 @@ function generateRepoModule(moduleName) {
189
384
  LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_INVALID);
190
385
  process.exit(1);
191
386
  }
192
- const modulePath = path.join("lib", "module", moduleName);
193
- // Verify module exists
194
- if (!fs.existsSync(modulePath)) {
195
- LoggerHelpers.error(`Module ${moduleName} does not exist at ${modulePath}. Create it first with: optikit generate module ${moduleName}`);
196
- process.exit(1);
197
- }
198
- // Create repo directory
199
- const repoPath = path.join(modulePath, "repo");
387
+ LoggerHelpers.info(`Generating repository for ${moduleName}...`);
388
+ LoggerHelpers.info("Select where to create the repo file:");
389
+ const startDir = fs.existsSync("lib") ? "lib" : ".";
390
+ const repoPath = await pickDirectory(startDir);
200
391
  if (!fs.existsSync(repoPath)) {
201
392
  fs.mkdirSync(repoPath, { recursive: true });
202
393
  }
203
- LoggerHelpers.info(`Adding repository to module ${moduleName}...`);
204
394
  generateRepo(moduleName, repoPath);
205
- // Append part directive to import file if not already present
206
- const importFilePath = path.join(modulePath, "import", `${moduleName}_import.dart`);
207
- if (fs.existsSync(importFilePath)) {
208
- const importContent = fs.readFileSync(importFilePath, "utf8");
209
- const repoPartLine = `part '../repo/${moduleName}_repo.dart';`;
210
- if (!importContent.includes(repoPartLine)) {
211
- const updatedContent = importContent.trimEnd() + `\n${repoPartLine}\n`;
212
- writeFile(importFilePath, updatedContent);
213
- LoggerHelpers.success(`Added repo part directive to ${moduleName}_import.dart`);
214
- }
215
- else {
216
- LoggerHelpers.info("Repo part directive already exists in import file.");
217
- }
218
- }
219
- else {
220
- LoggerHelpers.warning(`Import file not found at ${importFilePath}. Add the part directive manually.`);
221
- }
222
- LoggerHelpers.success(`Repository added to module ${moduleName}.`);
395
+ LoggerHelpers.success(`Repository created at ${path.join(repoPath, `${moduleName}_repo.dart`)}`);
223
396
  }
224
397
  catch (error) {
225
398
  handleCommandError(error, "Error generating repo");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "optikit",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "OptiKit CLI",
5
5
  "main": "src/cli.ts",
6
6
  "type": "module",
@@ -29,7 +29,7 @@ export const projectCommands: CommandModule[] = [
29
29
  builder: (yargs) => {
30
30
  return yargs.positional("moduleName", { describe: "The module name", type: "string" as const });
31
31
  },
32
- handler: (argv) => { generateRepoModule(argv.moduleName as string); },
32
+ handler: async (argv) => { await generateRepoModule(argv.moduleName as string); },
33
33
  },
34
34
  {
35
35
  command: "add-route <moduleName>",
@@ -1,5 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
+ import chalk from "chalk";
3
4
  import {
4
5
  createDirectories,
5
6
  writeFile,
@@ -44,7 +45,6 @@ function generateModule(moduleName: string) {
44
45
  generateScreen(moduleName, path.join(modulePath, "screen"));
45
46
  generateImport(moduleName, path.join(modulePath, "import"));
46
47
  generateStateFactory(moduleName, path.join(modulePath, "factory"));
47
- generateRepo(moduleName, path.join(modulePath, "repo"));
48
48
 
49
49
  LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
50
50
  } catch (error) {
@@ -170,7 +170,6 @@ part '../event/${moduleName}_event.dart';
170
170
  part '../screen/${moduleName}_screen.dart';
171
171
  part '../state/${moduleName}_state.dart';
172
172
  part '../factory/${moduleName}_factory.dart';
173
- part '../repo/${moduleName}_repo.dart';
174
173
  `;
175
174
 
176
175
  writeFile(importFilePath, template);
@@ -206,7 +205,7 @@ function generateRepo(moduleName: string, repoPath: string) {
206
205
  const repoFilePath = path.join(repoPath, `${moduleName}_repo.dart`);
207
206
  const className = getClassName(moduleName);
208
207
 
209
- const template = `part of '../import/${moduleName}_import.dart';
208
+ const template = `import 'package:opticore/opticore.dart';
210
209
 
211
210
  class ${className}Repo extends BaseRepo {
212
211
 
@@ -217,7 +216,7 @@ class ${className}Repo extends BaseRepo {
217
216
  );
218
217
  }
219
218
  }
220
- `;
219
+ `;
221
220
 
222
221
  writeFile(repoFilePath, template);
223
222
  LoggerHelpers.success(
@@ -226,12 +225,238 @@ class ${className}Repo extends BaseRepo {
226
225
  }
227
226
 
228
227
  /**
229
- * Generates a repository for an existing module.
230
- * Creates repo directory, generates repo file, and appends part directive to import file.
228
+ * Builds the list of selectable items for the directory picker.
229
+ */
230
+ interface PickerItem {
231
+ label: string;
232
+ icon: string;
233
+ action: "select" | "back" | "open" | "create";
234
+ dirName?: string;
235
+ }
236
+
237
+ function buildPickerItems(currentPath: string, rootPath: string): PickerItem[] {
238
+ const items: PickerItem[] = [];
239
+
240
+ // "Select here" always first
241
+ items.push({ label: "Select this directory", icon: "✔", action: "select" });
242
+
243
+ // Go back (unless at root)
244
+ const canGoBack = currentPath !== rootPath && currentPath !== ".";
245
+ if (canGoBack) {
246
+ items.push({ label: "..", icon: "↩", action: "back" });
247
+ }
248
+
249
+ // Subdirectories
250
+ if (fs.existsSync(currentPath)) {
251
+ const dirs = fs.readdirSync(currentPath, { withFileTypes: true })
252
+ .filter(e => e.isDirectory() && !e.name.startsWith('.'))
253
+ .map(e => e.name)
254
+ .sort();
255
+ for (const dir of dirs) {
256
+ items.push({ label: `${dir}/`, icon: "📁", action: "open", dirName: dir });
257
+ }
258
+ }
259
+
260
+ // Create new folder always last
261
+ items.push({ label: "Create new folder", icon: "✚", action: "create" });
262
+
263
+ return items;
264
+ }
265
+
266
+ /**
267
+ * Interactive arrow-key directory picker.
268
+ * Navigate with arrow keys, enter to select/open, esc to cancel.
269
+ */
270
+ function pickDirectory(startPath: string): Promise<string> {
271
+ return new Promise((resolve, reject) => {
272
+ let currentPath = startPath;
273
+ const rootPath = startPath;
274
+ let cursor = 0;
275
+ let items = buildPickerItems(currentPath, rootPath);
276
+ let totalLines = 0;
277
+ let creatingFolder = false;
278
+ let folderInput = "";
279
+
280
+ const stdin = process.stdin;
281
+ const wasRaw = stdin.isRaw ?? false;
282
+ stdin.setRawMode(true);
283
+ stdin.resume();
284
+ stdin.setEncoding("utf8");
285
+
286
+ function render() {
287
+ // Clear previous output
288
+ if (totalLines > 0) {
289
+ process.stdout.write(`\x1B[${totalLines}A`);
290
+ process.stdout.write("\x1B[0J");
291
+ }
292
+
293
+ const lines: string[] = [];
294
+
295
+ // Header — current path with breadcrumb
296
+ lines.push("");
297
+ lines.push(chalk.cyan.bold(` 📂 ${currentPath}/`));
298
+ lines.push(chalk.gray(" ─────────────────────────────────────"));
299
+
300
+ // Menu items
301
+ for (let i = 0; i < items.length; i++) {
302
+ const item = items[i];
303
+ const isActive = i === cursor;
304
+
305
+ if (isActive) {
306
+ const pointer = chalk.cyan.bold("❯");
307
+ const icon = item.action === "select" ? chalk.green(item.icon) :
308
+ item.action === "back" ? chalk.yellow(item.icon) :
309
+ item.action === "create" ? chalk.magenta(item.icon) :
310
+ chalk.blue(item.icon);
311
+ lines.push(` ${pointer} ${icon} ${chalk.white.bold(item.label)}`);
312
+ } else {
313
+ const icon = chalk.gray(item.icon);
314
+ lines.push(` ${icon} ${chalk.gray(item.label)}`);
315
+ }
316
+ }
317
+
318
+ lines.push(chalk.gray(" ─────────────────────────────────────"));
319
+
320
+ if (creatingFolder) {
321
+ lines.push(chalk.magenta(` ✚ New folder name: `) + chalk.white.bold(folderInput) + chalk.gray("▌"));
322
+ } else {
323
+ lines.push(chalk.gray(" ↑↓ navigate ⏎ select/open ← back esc cancel"));
324
+ }
325
+
326
+ const output = lines.join("\n") + "\n";
327
+ process.stdout.write(output);
328
+ totalLines = lines.length;
329
+ }
330
+
331
+ function cleanup() {
332
+ stdin.setRawMode(wasRaw);
333
+ stdin.removeListener("data", onKeypress);
334
+ stdin.pause();
335
+ }
336
+
337
+ function refreshItems() {
338
+ items = buildPickerItems(currentPath, rootPath);
339
+ cursor = 0;
340
+ }
341
+
342
+ function onKeypress(key: string) {
343
+ // Handle folder creation mode
344
+ if (creatingFolder) {
345
+ if (key === "\r") {
346
+ // Enter — create the folder
347
+ const trimmed = folderInput.trim();
348
+ if (trimmed.length > 0) {
349
+ const newPath = path.join(currentPath, trimmed);
350
+ fs.mkdirSync(newPath, { recursive: true });
351
+ currentPath = newPath;
352
+ refreshItems();
353
+ }
354
+ creatingFolder = false;
355
+ folderInput = "";
356
+ render();
357
+ } else if (key === "\x1B") {
358
+ // Esc — cancel folder creation
359
+ creatingFolder = false;
360
+ folderInput = "";
361
+ render();
362
+ } else if (key === "\x7F" || key === "\b") {
363
+ // Backspace
364
+ folderInput = folderInput.slice(0, -1);
365
+ render();
366
+ } else if (key.length === 1 && key.charCodeAt(0) >= 32) {
367
+ // Regular character
368
+ folderInput += key;
369
+ render();
370
+ }
371
+ return;
372
+ }
373
+
374
+ const item = items[cursor];
375
+
376
+ // Escape — cancel (single \x1B only, not arrow key sequences)
377
+ if (key === "\x1B" && key.length === 1) {
378
+ cleanup();
379
+ reject(new Error("Directory selection cancelled."));
380
+ return;
381
+ }
382
+
383
+ // Arrow keys
384
+ if (key === "\x1B[A") {
385
+ // Up
386
+ cursor = cursor > 0 ? cursor - 1 : items.length - 1;
387
+ render();
388
+ return;
389
+ }
390
+ if (key === "\x1B[B") {
391
+ // Down
392
+ cursor = cursor < items.length - 1 ? cursor + 1 : 0;
393
+ render();
394
+ return;
395
+ }
396
+ if (key === "\x1B[D") {
397
+ // Left arrow — go back
398
+ if (currentPath !== rootPath && currentPath !== ".") {
399
+ currentPath = path.dirname(currentPath);
400
+ refreshItems();
401
+ render();
402
+ }
403
+ return;
404
+ }
405
+ if (key === "\x1B[C") {
406
+ // Right arrow — open directory if cursor is on a folder
407
+ if (item.action === "open" && item.dirName) {
408
+ currentPath = path.join(currentPath, item.dirName);
409
+ refreshItems();
410
+ render();
411
+ }
412
+ return;
413
+ }
414
+
415
+ // Enter — perform action
416
+ if (key === "\r") {
417
+ if (item.action === "select") {
418
+ cleanup();
419
+ resolve(currentPath);
420
+ return;
421
+ }
422
+ if (item.action === "back") {
423
+ currentPath = path.dirname(currentPath);
424
+ refreshItems();
425
+ render();
426
+ return;
427
+ }
428
+ if (item.action === "open" && item.dirName) {
429
+ currentPath = path.join(currentPath, item.dirName);
430
+ refreshItems();
431
+ render();
432
+ return;
433
+ }
434
+ if (item.action === "create") {
435
+ creatingFolder = true;
436
+ folderInput = "";
437
+ render();
438
+ return;
439
+ }
440
+ }
441
+
442
+ // Ctrl+C
443
+ if (key === "\x03") {
444
+ cleanup();
445
+ process.exit(0);
446
+ }
447
+ }
448
+
449
+ stdin.on("data", onKeypress);
450
+ render();
451
+ });
452
+ }
453
+
454
+ /**
455
+ * Generates a repository file at a user-selected location.
456
+ * Opens an interactive directory picker starting from lib/.
231
457
  */
232
- function generateRepoModule(moduleName: string) {
458
+ async function generateRepoModule(moduleName: string) {
233
459
  try {
234
- // Validate module name
235
460
  if (!moduleName || moduleName.trim().length === 0) {
236
461
  LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_EMPTY);
237
462
  process.exit(1);
@@ -242,41 +467,19 @@ function generateRepoModule(moduleName: string) {
242
467
  process.exit(1);
243
468
  }
244
469
 
245
- const modulePath = path.join("lib", "module", moduleName);
470
+ LoggerHelpers.info(`Generating repository for ${moduleName}...`);
471
+ LoggerHelpers.info("Select where to create the repo file:");
246
472
 
247
- // Verify module exists
248
- if (!fs.existsSync(modulePath)) {
249
- LoggerHelpers.error(`Module ${moduleName} does not exist at ${modulePath}. Create it first with: optikit generate module ${moduleName}`);
250
- process.exit(1);
251
- }
473
+ const startDir = fs.existsSync("lib") ? "lib" : ".";
474
+ const repoPath = await pickDirectory(startDir);
252
475
 
253
- // Create repo directory
254
- const repoPath = path.join(modulePath, "repo");
255
476
  if (!fs.existsSync(repoPath)) {
256
477
  fs.mkdirSync(repoPath, { recursive: true });
257
478
  }
258
479
 
259
- LoggerHelpers.info(`Adding repository to module ${moduleName}...`);
260
480
  generateRepo(moduleName, repoPath);
261
481
 
262
- // Append part directive to import file if not already present
263
- const importFilePath = path.join(modulePath, "import", `${moduleName}_import.dart`);
264
- if (fs.existsSync(importFilePath)) {
265
- const importContent = fs.readFileSync(importFilePath, "utf8");
266
- const repoPartLine = `part '../repo/${moduleName}_repo.dart';`;
267
-
268
- if (!importContent.includes(repoPartLine)) {
269
- const updatedContent = importContent.trimEnd() + `\n${repoPartLine}\n`;
270
- writeFile(importFilePath, updatedContent);
271
- LoggerHelpers.success(`Added repo part directive to ${moduleName}_import.dart`);
272
- } else {
273
- LoggerHelpers.info("Repo part directive already exists in import file.");
274
- }
275
- } else {
276
- LoggerHelpers.warning(`Import file not found at ${importFilePath}. Add the part directive manually.`);
277
- }
278
-
279
- LoggerHelpers.success(`Repository added to module ${moduleName}.`);
482
+ LoggerHelpers.success(`Repository created at ${path.join(repoPath, `${moduleName}_repo.dart`)}`);
280
483
  } catch (error) {
281
484
  handleCommandError(error, "Error generating repo");
282
485
  }