murmur8 4.1.1 → 4.3.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 (50) hide show
  1. package/.blueprint/agents/AGENT_SPECIFICATION_ALEX.md +33 -3
  2. package/.blueprint/features/feature_config-factory/FEATURE_SPEC.md +138 -0
  3. package/.blueprint/features/feature_config-factory/IMPLEMENTATION_PLAN.md +187 -0
  4. package/.blueprint/features/feature_config-factory/handoff-nigel.md +57 -0
  5. package/.blueprint/features/feature_export-history/FEATURE_SPEC.md +215 -0
  6. package/.blueprint/features/feature_export-history/IMPLEMENTATION_PLAN.md +48 -0
  7. package/.blueprint/features/feature_export-history/story-basic-export.md +48 -0
  8. package/.blueprint/features/feature_export-history/story-date-filter.md +42 -0
  9. package/.blueprint/features/feature_export-history/story-feature-filter.md +42 -0
  10. package/.blueprint/features/feature_export-history/story-file-output.md +48 -0
  11. package/.blueprint/features/feature_export-history/story-status-filter.md +42 -0
  12. package/.blueprint/features/feature_extract-prompt-util/FEATURE_SPEC.md +42 -0
  13. package/.blueprint/features/feature_fix-status-icons/FEATURE_SPEC.md +37 -0
  14. package/.blueprint/features/feature_murm-subagent/FEATURE_SPEC.md +137 -0
  15. package/.blueprint/features/feature_murm-subagent/SKILL_CHANGES.md +345 -0
  16. package/.blueprint/features/feature_split-cli-commands/FEATURE_SPEC.md +125 -0
  17. package/.blueprint/features/feature_split-cli-commands/IMPLEMENTATION_PLAN.md +119 -0
  18. package/.blueprint/features/feature_split-cli-commands/handoff-nigel.md +45 -0
  19. package/.blueprint/features/feature_theme-adoption/FEATURE_SPEC.md +143 -0
  20. package/.blueprint/features/feature_theme-adoption/IMPLEMENTATION_PLAN.md +68 -0
  21. package/.blueprint/features/feature_theme-adoption/handoff-nigel.md +35 -0
  22. package/.blueprint/templates/BACKLOG_TEMPLATE.md +46 -0
  23. package/README.md +26 -10
  24. package/SKILL.md +377 -3
  25. package/bin/cli.js +20 -384
  26. package/package.json +1 -1
  27. package/src/commands/feedback-config.js +32 -0
  28. package/src/commands/help.js +81 -0
  29. package/src/commands/history.js +42 -0
  30. package/src/commands/init.js +12 -0
  31. package/src/commands/insights.js +23 -0
  32. package/src/commands/murm-config.js +52 -0
  33. package/src/commands/murm.js +109 -0
  34. package/src/commands/queue.js +19 -0
  35. package/src/commands/retry-config.js +28 -0
  36. package/src/commands/stack-config.js +32 -0
  37. package/src/commands/update.js +12 -0
  38. package/src/commands/utils.js +24 -0
  39. package/src/commands/validate.js +15 -0
  40. package/src/config-factory.js +190 -0
  41. package/src/feedback.js +5 -2
  42. package/src/history.js +92 -1
  43. package/src/init.js +1 -15
  44. package/src/insights.js +19 -16
  45. package/src/retry.js +5 -2
  46. package/src/stack.js +4 -1
  47. package/src/theme.js +4 -4
  48. package/src/update.js +2 -15
  49. package/src/utils.js +26 -0
  50. package/src/validate.js +5 -12
@@ -0,0 +1,109 @@
1
+ /**
2
+ * murm command - Run multiple feature pipelines in parallel using git worktrees
3
+ */
4
+ const {
5
+ formatStatus,
6
+ runMurm,
7
+ loadQueue,
8
+ cleanupWorktrees,
9
+ abortMurm,
10
+ getLockInfo,
11
+ getDetailedStatus,
12
+ formatDetailedStatus,
13
+ rollbackMurm
14
+ } = require('../murm');
15
+
16
+ const description = 'Run multiple feature pipelines in parallel using git worktrees';
17
+ const aliases = ['parallel', 'murmuration'];
18
+
19
+ async function run(args) {
20
+ const subArg = args[1];
21
+
22
+ if (subArg === 'status') {
23
+ const detailed = args.includes('--detailed') || args.includes('-d');
24
+ const lock = getLockInfo();
25
+
26
+ if (detailed) {
27
+ const details = getDetailedStatus();
28
+ console.log(formatDetailedStatus(details));
29
+ } else {
30
+ const queue = loadQueue();
31
+
32
+ if (!queue.features || queue.features.length === 0) {
33
+ if (lock) {
34
+ console.log(`Murmuration execution in progress (PID: ${lock.pid})`);
35
+ console.log(`Started: ${lock.startedAt}`);
36
+ console.log(`Features: ${lock.features.join(', ')}`);
37
+ } else {
38
+ console.log('No murmuration pipelines active.');
39
+ }
40
+ return;
41
+ }
42
+
43
+ console.log('Murmuration Pipeline Status\n');
44
+ console.log(formatStatus(queue.features));
45
+ const summary = {
46
+ running: queue.features.filter(f => f.status === 'murm_running').length,
47
+ pending: queue.features.filter(f => f.status === 'murm_queued').length,
48
+ completed: queue.features.filter(f => f.status === 'murm_complete').length,
49
+ failed: queue.features.filter(f => f.status === 'murm_failed').length,
50
+ conflicts: queue.features.filter(f => f.status === 'merge_conflict').length
51
+ };
52
+ console.log(`\nRunning: ${summary.running} | Pending: ${summary.pending} | Completed: ${summary.completed} | Failed: ${summary.failed} | Conflicts: ${summary.conflicts}`);
53
+
54
+ // Show log paths for running/failed
55
+ const withLogs = queue.features.filter(f =>
56
+ f.logPath && (f.status === 'murm_running' || f.status === 'murm_failed')
57
+ );
58
+ if (withLogs.length > 0) {
59
+ console.log('\nLog files:');
60
+ withLogs.forEach(f => console.log(` ${f.slug}: ${f.logPath}`));
61
+ }
62
+
63
+ console.log('\nTip: Use --detailed for progress bars');
64
+ }
65
+ } else if (subArg === 'rollback') {
66
+ const dryRunFlag = args.includes('--dry-run');
67
+ const forceFlag = args.includes('--force');
68
+ await rollbackMurm({ dryRun: dryRunFlag, force: forceFlag });
69
+ } else if (subArg === 'cleanup') {
70
+ const cleaned = await cleanupWorktrees();
71
+ console.log(`Cleaned ${cleaned} worktree(s).`);
72
+ } else if (subArg === 'abort') {
73
+ const cleanupFlag = args.includes('--cleanup');
74
+ await abortMurm({ cleanup: cleanupFlag });
75
+ } else {
76
+ const slugs = args.slice(1).filter(a => !a.startsWith('--') && !a.startsWith('-'));
77
+ if (slugs.length === 0) {
78
+ console.error('Usage: murmur8 murm <slug1> <slug2> ... [options]');
79
+ console.error('\nOptions:');
80
+ console.error(' --dry-run Preview execution plan without running');
81
+ console.error(' --yes, -y Skip confirmation prompt');
82
+ console.error(' --force Override existing lock');
83
+ console.error(' --verbose Stream output to console (not just logs)');
84
+ console.error(' --skip-preflight Skip feature validation checks');
85
+ console.error(' --max-concurrency=N Set max parallel pipelines (default: 3)');
86
+ console.error('\nSubcommands:');
87
+ console.error(' murm status Show status of all pipelines');
88
+ console.error(' murm abort Stop all running pipelines');
89
+ console.error(' murm cleanup Remove completed/aborted worktrees');
90
+ process.exit(1);
91
+ }
92
+
93
+ const maxFlag = args.find(a => a.startsWith('--max-concurrency='));
94
+ const options = {
95
+ dryRun: args.includes('--dry-run'),
96
+ yes: args.includes('--yes') || args.includes('-y'),
97
+ force: args.includes('--force'),
98
+ verbose: args.includes('--verbose'),
99
+ skipPreflight: args.includes('--skip-preflight')
100
+ };
101
+ if (maxFlag) {
102
+ options.maxConcurrency = parseInt(maxFlag.split('=')[1], 10);
103
+ }
104
+ const result = await runMurm(slugs, options);
105
+ process.exit(result.success ? 0 : 1);
106
+ }
107
+ }
108
+
109
+ module.exports = { run, description, aliases };
@@ -0,0 +1,19 @@
1
+ /**
2
+ * queue command - Show queue status or reset
3
+ */
4
+ const { displayQueue, resetQueue } = require('../orchestrator');
5
+
6
+ const description = 'Show queue status (use "reset" to clear)';
7
+
8
+ async function run(args) {
9
+ const subArg = args[1];
10
+
11
+ if (subArg === 'reset') {
12
+ resetQueue();
13
+ console.log('Queue has been reset.');
14
+ } else {
15
+ displayQueue();
16
+ }
17
+ }
18
+
19
+ module.exports = { run, description };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * retry-config command - Manage retry configuration for adaptive retry logic
3
+ */
4
+ const { displayConfig, setConfigValue, resetConfig } = require('../retry');
5
+
6
+ const description = 'Manage retry configuration for adaptive retry logic';
7
+
8
+ async function run(args) {
9
+ const subArg = args[1];
10
+
11
+ if (subArg === 'set') {
12
+ const key = args[2];
13
+ const value = args[3];
14
+ if (!key || !value) {
15
+ console.error('Usage: retry-config set <key> <value>');
16
+ console.error('Valid keys: maxRetries, windowSize, highFailureThreshold');
17
+ process.exit(1);
18
+ }
19
+ setConfigValue(key, value);
20
+ } else if (subArg === 'reset') {
21
+ resetConfig();
22
+ console.log('Retry configuration reset to defaults.');
23
+ } else {
24
+ displayConfig();
25
+ }
26
+ }
27
+
28
+ module.exports = { run, description };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * stack-config command - View or modify project tech stack configuration
3
+ */
4
+ const {
5
+ displayStackConfig,
6
+ setStackConfigValue,
7
+ resetStackConfig
8
+ } = require('../stack');
9
+
10
+ const description = 'View or modify project tech stack configuration';
11
+
12
+ async function run(args) {
13
+ const subArg = args[1];
14
+
15
+ if (subArg === 'set') {
16
+ const key = args[2];
17
+ const value = args[3];
18
+ if (!key || !value) {
19
+ console.error('Usage: stack-config set <key> <value>');
20
+ console.error('Valid keys: language, runtime, packageManager, frameworks, testRunner, testCommand, linter, tools');
21
+ process.exit(1);
22
+ }
23
+ setStackConfigValue(key, value);
24
+ } else if (subArg === 'reset') {
25
+ resetStackConfig();
26
+ console.log('Stack configuration reset to defaults.');
27
+ } else {
28
+ displayStackConfig();
29
+ }
30
+ }
31
+
32
+ module.exports = { run, description };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * update command - Update agents, templates, and rituals (preserves your content)
3
+ */
4
+ const { update } = require('../update');
5
+
6
+ const description = 'Update agents, templates, and rituals (preserves your content)';
7
+
8
+ async function run(args) {
9
+ await update();
10
+ }
11
+
12
+ module.exports = { run, description };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared utilities for CLI commands
3
+ */
4
+
5
+ /**
6
+ * Parse common flags from command line arguments
7
+ * @param {string[]} args - Command line arguments
8
+ * @returns {Object} Parsed flags
9
+ */
10
+ function parseFlags(args) {
11
+ const flags = {};
12
+ for (const arg of args) {
13
+ if (arg === '--all') flags.all = true;
14
+ if (arg === '--stats') flags.stats = true;
15
+ if (arg === '--force') flags.force = true;
16
+ if (arg === '--bottlenecks') flags.bottlenecks = true;
17
+ if (arg === '--failures') flags.failures = true;
18
+ if (arg === '--json') flags.json = true;
19
+ if (arg === '--feedback') flags.feedback = true;
20
+ }
21
+ return flags;
22
+ }
23
+
24
+ module.exports = { parseFlags };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * validate command - Run pre-flight checks to validate project configuration
3
+ */
4
+ const { validate, formatOutput } = require('../validate');
5
+
6
+ const description = 'Run pre-flight checks to validate project configuration';
7
+
8
+ async function run(args) {
9
+ const result = await validate();
10
+ const useColor = process.stdout.isTTY || false;
11
+ console.log(formatOutput(result, useColor));
12
+ process.exit(result.exitCode);
13
+ }
14
+
15
+ module.exports = { run, description };
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * Factory function to create a standardized config module.
8
+ *
9
+ * @param {Object} options - Configuration options
10
+ * @param {string} options.name - Display name for the config (e.g., 'Retry', 'Feedback')
11
+ * @param {string} options.file - Path to config file (e.g., '.claude/retry-config.json')
12
+ * @param {Object} options.defaults - Default configuration values
13
+ * @param {Object} [options.validators] - Map of key -> validator function
14
+ * Validator returns true for valid, or error string for invalid
15
+ * @param {Object} [options.formatters] - Map of key -> display formatter function
16
+ * @param {string[]} [options.arrayKeys] - Keys that accept JSON array values
17
+ * @returns {Object} Config module with standard methods
18
+ */
19
+ function createConfigModule(options) {
20
+ const {
21
+ name,
22
+ file,
23
+ defaults,
24
+ validators = {},
25
+ formatters = {},
26
+ arrayKeys = []
27
+ } = options;
28
+
29
+ /**
30
+ * Ensures the config directory exists.
31
+ */
32
+ function ensureConfigDir() {
33
+ const dir = path.dirname(file);
34
+ if (!fs.existsSync(dir)) {
35
+ fs.mkdirSync(dir, { recursive: true });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Returns a copy of the default configuration.
41
+ */
42
+ function getDefault() {
43
+ return JSON.parse(JSON.stringify(defaults));
44
+ }
45
+
46
+ /**
47
+ * Reads the config from file.
48
+ * Returns defaults if file is missing or corrupted.
49
+ * Merges missing keys from defaults.
50
+ */
51
+ function read() {
52
+ ensureConfigDir();
53
+ if (!fs.existsSync(file)) {
54
+ return getDefault();
55
+ }
56
+ try {
57
+ const content = fs.readFileSync(file, 'utf8');
58
+ const parsed = JSON.parse(content);
59
+ // Merge missing keys from defaults
60
+ const merged = { ...getDefault(), ...parsed };
61
+ return merged;
62
+ } catch (err) {
63
+ // Graceful degradation: return defaults on parse error
64
+ return getDefault();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Writes the config to file.
70
+ * Creates directory if needed.
71
+ */
72
+ function write(config) {
73
+ ensureConfigDir();
74
+ fs.writeFileSync(file, JSON.stringify(config, null, 2));
75
+ }
76
+
77
+ /**
78
+ * Resets config to defaults by writing default config to file.
79
+ */
80
+ function reset() {
81
+ write(getDefault());
82
+ }
83
+
84
+ /**
85
+ * Sets a config value by key.
86
+ * @param {string} key - Config key
87
+ * @param {string} value - New value (will be parsed appropriately)
88
+ */
89
+ function setValue(key, value) {
90
+ const validKeys = Object.keys(defaults);
91
+
92
+ if (!validKeys.includes(key)) {
93
+ throw new Error(
94
+ `Unknown config key: ${key}. Valid keys: ${validKeys.join(', ')}`
95
+ );
96
+ }
97
+
98
+ let parsed = value;
99
+
100
+ // Handle array keys
101
+ if (arrayKeys.includes(key)) {
102
+ try {
103
+ parsed = JSON.parse(value);
104
+ if (!Array.isArray(parsed)) {
105
+ throw new Error(`${key} must be a JSON array, e.g. '["a","b"]'`);
106
+ }
107
+ } catch (err) {
108
+ if (err.message.includes('must be a JSON array')) throw err;
109
+ throw new Error(`${key} must be a valid JSON array, e.g. '["a","b"]'`);
110
+ }
111
+ } else {
112
+ // Type coercion based on default value type
113
+ const defaultType = typeof defaults[key];
114
+ if (defaultType === 'number') {
115
+ parsed = parseFloat(value);
116
+ if (isNaN(parsed)) {
117
+ throw new Error(`Invalid value for ${key}: ${value}. Must be a number.`);
118
+ }
119
+ } else if (defaultType === 'boolean') {
120
+ if (value !== 'true' && value !== 'false') {
121
+ throw new Error(`Invalid value for ${key}: ${value}. Must be true or false.`);
122
+ }
123
+ parsed = value === 'true';
124
+ }
125
+ }
126
+
127
+ // Run custom validator if provided
128
+ if (validators[key]) {
129
+ const result = validators[key](parsed);
130
+ if (result !== true) {
131
+ throw new Error(`Invalid value for ${key}: ${value}. ${result}`);
132
+ }
133
+ }
134
+
135
+ const config = read();
136
+ config[key] = parsed;
137
+ write(config);
138
+
139
+ // Log confirmation
140
+ const display = Array.isArray(config[key]) ? JSON.stringify(config[key]) : config[key];
141
+ console.log(`Set ${key} = ${display}`);
142
+ }
143
+
144
+ /**
145
+ * Displays the current configuration.
146
+ */
147
+ function display() {
148
+ const config = read();
149
+ console.log(`\n${name} Configuration\n`);
150
+
151
+ for (const [key, value] of Object.entries(config)) {
152
+ let displayValue;
153
+
154
+ // Use custom formatter if available
155
+ if (formatters[key]) {
156
+ displayValue = formatters[key](value);
157
+ } else if (Array.isArray(value)) {
158
+ displayValue = value.length > 0 ? value.join(', ') : '(not set)';
159
+ } else if (typeof value === 'object' && value !== null) {
160
+ // For nested objects, format each entry
161
+ console.log(`\n ${key}:`);
162
+ for (const [subKey, subValue] of Object.entries(value)) {
163
+ const subDisplay = Array.isArray(subValue) ? subValue.join(' -> ') : subValue;
164
+ console.log(` ${subKey.padEnd(20)}: ${subDisplay}`);
165
+ }
166
+ continue;
167
+ } else if (value === '' || value === null || value === undefined) {
168
+ displayValue = '(not set)';
169
+ } else {
170
+ displayValue = String(value);
171
+ }
172
+
173
+ console.log(` ${key.padEnd(20)}: ${displayValue}`);
174
+ }
175
+
176
+ console.log('');
177
+ }
178
+
179
+ return {
180
+ CONFIG_FILE: file,
181
+ getDefault,
182
+ read,
183
+ write,
184
+ reset,
185
+ setValue,
186
+ display
187
+ };
188
+ }
189
+
190
+ module.exports = { createConfigModule };
package/src/feedback.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const { colorize } = require('./theme');
3
4
 
4
5
  const CONFIG_FILE = '.claude/feedback-config.json';
5
6
 
@@ -169,10 +170,12 @@ function setConfigValue(key, value) {
169
170
  */
170
171
  function displayConfig() {
171
172
  const config = readConfig();
172
- console.log('\nFeedback Configuration\n');
173
+ const useColor = process.stdout.isTTY;
174
+
175
+ console.log('\n' + colorize('Feedback Configuration', 'cyan', useColor) + '\n');
173
176
  console.log(` Min rating threshold: ${config.minRatingThreshold}`);
174
177
  console.log(` Enabled: ${config.enabled}`);
175
- console.log('\n Issue Mappings:');
178
+ console.log('\n ' + colorize('Issue Mappings:', 'cyan', useColor));
176
179
  for (const [issue, strategy] of Object.entries(config.issueMappings)) {
177
180
  console.log(` ${issue.padEnd(24)}: ${strategy}`);
178
181
  }
package/src/history.js CHANGED
@@ -100,6 +100,96 @@ function formatDuration(ms) {
100
100
  return `${minutes}m ${secs}s`;
101
101
  }
102
102
 
103
+ function isValidDateFormat(dateStr) {
104
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return false;
105
+ const date = new Date(dateStr);
106
+ return !isNaN(date.getTime());
107
+ }
108
+
109
+ function escapeCSVField(value) {
110
+ if (value === null || value === undefined) return '';
111
+ const str = String(value);
112
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
113
+ return `"${str.replace(/"/g, '""')}"`;
114
+ }
115
+ return str;
116
+ }
117
+
118
+ function formatCSV(entries) {
119
+ const headers = ['slug', 'status', 'startedAt', 'completedAt', 'totalDurationMs', 'failedStage', 'pausedAfter'];
120
+ const lines = [headers.join(',')];
121
+ for (const entry of entries) {
122
+ const row = headers.map(h => escapeCSVField(entry[h]));
123
+ lines.push(row.join(','));
124
+ }
125
+ return lines.join('\n');
126
+ }
127
+
128
+ function formatJSON(entries) {
129
+ return JSON.stringify(entries, null, 2);
130
+ }
131
+
132
+ async function exportHistory(options = {}) {
133
+ const { format = 'csv', since, until, status, feature, output } = options;
134
+
135
+ const history = readHistoryFile();
136
+
137
+ if (history.error === 'corrupted') {
138
+ return { exitCode: 1, error: "History file is corrupted. Run 'murmur8 history clear' to reset." };
139
+ }
140
+
141
+ if (since && !isValidDateFormat(since)) {
142
+ return { exitCode: 1, error: 'Invalid --since format. Use YYYY-MM-DD.' };
143
+ }
144
+
145
+ if (until && !isValidDateFormat(until)) {
146
+ return { exitCode: 1, error: 'Invalid --until format. Use YYYY-MM-DD.' };
147
+ }
148
+
149
+ const validStatuses = ['success', 'failed', 'paused'];
150
+ if (status && !validStatuses.includes(status)) {
151
+ return { exitCode: 1, error: `Invalid --status value. Use: success, failed, paused.` };
152
+ }
153
+
154
+ let filtered = history;
155
+
156
+ if (since) {
157
+ const sinceDate = new Date(since);
158
+ filtered = filtered.filter(e => e.completedAt && new Date(e.completedAt) >= sinceDate);
159
+ }
160
+
161
+ if (until) {
162
+ const untilDate = new Date(until);
163
+ untilDate.setDate(untilDate.getDate() + 1);
164
+ filtered = filtered.filter(e => e.completedAt && new Date(e.completedAt) < untilDate);
165
+ }
166
+
167
+ if (status) {
168
+ filtered = filtered.filter(e => e.status === status);
169
+ }
170
+
171
+ if (feature) {
172
+ filtered = filtered.filter(e => e.slug === feature);
173
+ }
174
+
175
+ const formatted = format === 'json' ? formatJSON(filtered) : formatCSV(filtered);
176
+
177
+ if (output) {
178
+ try {
179
+ const dir = path.dirname(output);
180
+ if (!fs.existsSync(dir)) {
181
+ fs.mkdirSync(dir, { recursive: true });
182
+ }
183
+ fs.writeFileSync(output, formatted);
184
+ return { message: `Exported ${filtered.length} entries to ${output}` };
185
+ } catch (err) {
186
+ return { exitCode: 1, error: err.message };
187
+ }
188
+ }
189
+
190
+ return { output: formatted };
191
+ }
192
+
103
193
  function formatDate(isoString) {
104
194
  const date = new Date(isoString);
105
195
  return date.toISOString().replace('T', ' ').slice(0, 19);
@@ -292,5 +382,6 @@ module.exports = {
292
382
  displayHistory,
293
383
  showStats,
294
384
  clearHistory,
295
- formatDuration
385
+ formatDuration,
386
+ exportHistory
296
387
  };
package/src/init.js CHANGED
@@ -1,26 +1,12 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const readline = require('readline');
4
3
 
5
4
  const { detectStackConfig, writeStackConfig, CONFIG_FILE: STACK_CONFIG_FILE } = require('./stack');
5
+ const { prompt } = require('./utils');
6
6
 
7
7
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
8
8
  const TARGET_DIR = process.cwd();
9
9
 
10
- async function prompt(question) {
11
- const rl = readline.createInterface({
12
- input: process.stdin,
13
- output: process.stdout
14
- });
15
-
16
- return new Promise((resolve) => {
17
- rl.question(question, (answer) => {
18
- rl.close();
19
- resolve(answer.toLowerCase().trim());
20
- });
21
- });
22
- }
23
-
24
10
  function copyDir(src, dest) {
25
11
  fs.mkdirSync(dest, { recursive: true });
26
12