@shirayner/ace 0.1.1-snapshot.1 → 0.1.1-snapshot.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shirayner/ace",
3
- "version": "0.1.1-snapshot.1",
3
+ "version": "0.1.1-snapshot.3",
4
4
  "description": "AI Coding Environment - One command to set up your Claude Code harness",
5
5
  "bin": {
6
6
  "ace": "./bin/ace.js"
@@ -1,88 +1,55 @@
1
- import chalk from 'chalk';
2
1
  import inquirer from 'inquirer';
2
+ import ora from 'ora';
3
3
  import { createRequire } from 'module';
4
4
  import { PRESETS, ROLES, COMPONENTS } from '../core/constants.js';
5
5
  import { Installer } from '../core/installer.js';
6
6
  import {
7
- printBanner, sectionHeader, stepDone, stepSkip, stepFail,
8
- fileEntry, summaryBox, doneMessage, doneWithErrors, separator,
9
- conflictHeader, conflictFile,
10
- colors, icons, componentIcons, componentLabels,
7
+ printBanner, renderScreen,
8
+ doneMessage, doneWithErrors,
9
+ colors, icons, componentLabels,
11
10
  } from '../core/ui.js';
12
11
 
13
12
  const require = createRequire(import.meta.url);
14
13
  const pkg = require('../../package.json');
15
14
 
16
- export async function initCommand(options) {
17
- // ─── Banner ──────────────────────────────────────────────────────
18
- printBanner(pkg.version);
15
+ function formatStep(label, value) {
16
+ return ` ${colors.success(icons.check)} ${colors.dim(label)} ${colors.dim('·')} ${colors.white(value)}`;
17
+ }
19
18
 
19
+ export async function initCommand(options) {
20
+ const version = pkg.version;
20
21
  let role = 'fullstack';
21
22
  let preset = options.preset;
23
+ const completedSteps = [];
22
24
 
23
- // ─── Interactive prompts ─────────────────────────────────────────
25
+ // ─── Step 1: Role ──────────────────────────────────────────
24
26
  if (options.interaction !== false) {
25
- const answers = await inquirer.prompt([
26
- {
27
- type: 'list',
28
- name: 'role',
29
- message: `${icons.gear} Your primary role?`,
30
- choices: Object.entries(ROLES).map(([key, val]) => ({
31
- name: `${colors.white(val.label)} ${colors.dim('—')} ${colors.muted(val.description)}`,
32
- value: key,
33
- short: val.label,
34
- })),
35
- default: 'fullstack',
36
- prefix: colors.brand('?'),
37
- },
38
- {
39
- type: 'list',
40
- name: 'preset',
41
- message: `${icons.package} Installation scope?`,
42
- choices: [
43
- {
44
- name: `${colors.white('Full')} ${colors.dim('—')} ${colors.muted('All components (rules, plugin, hooks, safety guards, memory)')}`,
45
- value: 'full',
46
- short: 'Full',
47
- },
48
- {
49
- name: `${colors.white('Safe')} ${colors.dim('—')} ${colors.muted('Core + rules + plugin + safety guards + memory')}`,
50
- value: 'safe',
51
- short: 'Safe',
52
- },
53
- {
54
- name: `${colors.white('Minimal')} ${colors.dim('—')} ${colors.muted('Core + rules + plugin only')}`,
55
- value: 'minimal',
56
- short: 'Minimal',
57
- },
58
- ],
59
- default: 'full',
60
- prefix: colors.brand('?'),
61
- },
62
- ]);
63
- role = answers.role;
64
- preset = answers.preset;
27
+ renderScreen(version);
28
+
29
+ const result = await inquirer.prompt([{
30
+ type: 'list',
31
+ name: 'role',
32
+ message: 'Role',
33
+ choices: Object.entries(ROLES).map(([key, val]) => ({
34
+ name: `${colors.white(val.label)} ${colors.dim(val.description)}`,
35
+ value: key,
36
+ short: val.label,
37
+ })),
38
+ default: 'fullstack',
39
+ prefix: colors.brand('?'),
40
+ }]);
41
+
42
+ role = result.role;
43
+ completedSteps.push(formatStep('Role', ROLES[role].label));
65
44
  }
66
45
 
67
46
  const components = PRESETS[preset];
68
47
  if (!components) {
69
- console.error(colors.error(` ${icons.cross} Unknown preset: ${preset}. Available: ${Object.keys(PRESETS).join(', ')}`));
48
+ console.error(` ${colors.error(icons.cross)} Unknown preset: ${preset}. Use: ${Object.keys(PRESETS).join(', ')}`);
70
49
  process.exit(1);
71
50
  }
72
51
 
73
- // ─── Config summary ──────────────────────────────────────────────
74
- console.log();
75
- console.log(` ${colors.dim('│')} ${colors.dim('Role')} ${colors.white(ROLES[role].label)}`);
76
- console.log(` ${colors.dim('│')} ${colors.dim('Preset')} ${colors.white(preset)}`);
77
- console.log(` ${colors.dim('│')} ${colors.dim('Scope')} ${components.map(c => componentLabels[c] || c).join(colors.dim(', '))}`);
78
- if (options.force) {
79
- console.log(` ${colors.dim('│')} ${colors.warning(`${icons.warn} Force mode — existing files will be overwritten`)}`);
80
- }
81
- if (options.dryRun) {
82
- console.log(` ${colors.dim('│')} ${colors.accent(`${icons.info} Dry-run mode — no changes will be made`)}`);
83
- }
84
-
85
- // ─── Conflict detection & category confirmation ──────────────────
52
+ // ─── Step 2: Conflicts (conditional) ───────────────────────
86
53
  const installer = new Installer({
87
54
  force: options.force,
88
55
  dryRun: options.dryRun,
@@ -98,118 +65,115 @@ export async function initCommand(options) {
98
65
  const conflictKeys = Object.keys(conflicts);
99
66
 
100
67
  if (conflictKeys.length > 0) {
68
+ const totalFiles = conflictKeys.reduce((sum, k) => sum + conflicts[k].files.length, 0);
69
+
70
+ renderScreen(version, completedSteps);
71
+ console.log(` ${colors.warning(icons.warn)} ${totalFiles} existing file(s) found`);
101
72
  console.log();
102
- sectionHeader(icons.warn, 'Existing files detected');
103
- console.log(` ${colors.dim('│')} ${colors.muted('Some files already exist. Choose how to handle each category:')}`);
104
- console.log(` ${colors.dim('')} ${colors.muted(`CLAUDE.md ${icons.arrowR} always smart-merge (append new refs only)`)}`);
105
- console.log(` ${colors.dim('')} ${colors.muted(`settings.json ${icons.arrowR} always deep-merge (preserve your settings)`)}`);
106
-
107
- for (const componentName of conflictKeys) {
108
- const { files } = conflicts[componentName];
109
- const icon = componentIcons[componentName] || icons.file;
110
- const label = componentLabels[componentName] || componentName;
111
-
112
- conflictHeader(label, icon, files.length);
113
- for (const f of files) {
114
- conflictFile(f.replace(/\\/g, '/'));
115
- }
73
+
74
+ const { action } = await inquirer.prompt([{
75
+ type: 'list',
76
+ name: 'action',
77
+ message: 'How to handle?',
78
+ choices: [
79
+ {
80
+ name: `${colors.white('Keep existing')} ${colors.dim('merge compatible, skip rest')}`,
81
+ value: 'skip',
82
+ short: 'Keep',
83
+ },
84
+ {
85
+ name: `${colors.warning('Overwrite all')} ${colors.dim('replace with latest')}`,
86
+ value: 'overwrite',
87
+ short: 'Overwrite',
88
+ },
89
+ ],
90
+ default: 'skip',
91
+ prefix: colors.brand('?'),
92
+ }]);
93
+
94
+ for (const key of conflictKeys) {
95
+ resolutions[key] = action;
116
96
  }
117
97
 
118
- console.log();
119
- const conflictAnswers = await inquirer.prompt(
120
- conflictKeys.map((componentName) => {
121
- const icon = componentIcons[componentName] || icons.file;
122
- const label = componentLabels[componentName] || componentName;
123
- const count = conflicts[componentName].files.length;
124
- return {
125
- type: 'list',
126
- name: componentName,
127
- message: `${icon} ${label} (${count} files)`,
128
- choices: [
129
- {
130
- name: `${colors.muted('Skip')} ${colors.dim('— keep existing files')}`,
131
- value: 'skip',
132
- short: 'Skip',
133
- },
134
- {
135
- name: `${colors.warning('Overwrite')} ${colors.dim('— replace with ace templates')}`,
136
- value: 'overwrite',
137
- short: 'Overwrite',
138
- },
139
- ],
140
- default: 'skip',
141
- prefix: colors.brand('?'),
142
- };
143
- })
98
+ completedSteps.push(
99
+ formatStep('Conflicts', action === 'skip' ? 'Keep existing' : 'Overwrite all')
144
100
  );
145
-
146
- resolutions = conflictAnswers;
147
101
  }
148
102
  }
149
103
 
150
- // Apply resolutions to installer
151
104
  installer.resolutions = resolutions;
152
105
 
153
- // ─── Installation ────────────────────────────────────────────────
154
- console.log();
155
- sectionHeader(icons.rocket, 'Installing components');
106
+ // ─── Step 3: Install ───────────────────────────────────────
107
+ if (options.interaction !== false) {
108
+ renderScreen(version, completedSteps);
109
+ } else {
110
+ printBanner(version);
111
+ }
112
+
113
+ if (options.dryRun) {
114
+ console.log(` ${colors.dim('dry-run — no changes will be made')}`);
115
+ console.log();
116
+ }
156
117
 
157
118
  for (const componentName of components) {
158
119
  const component = COMPONENTS[componentName];
159
120
  if (!component) continue;
160
121
 
161
- const icon = componentIcons[componentName] || icons.file;
162
122
  const label = componentLabels[componentName] || componentName;
163
-
164
123
  const beforeInstalled = installer.results.installed.length;
165
124
  const beforeMerged = installer.results.merged.length;
166
125
  const beforeSkipped = installer.results.skipped.length;
167
126
 
127
+ const spinner = ora({
128
+ text: label,
129
+ indent: 2,
130
+ color: 'magenta',
131
+ }).start();
132
+
168
133
  try {
169
134
  await installer.installComponent(componentName, component);
135
+
170
136
  const newInstalled = installer.results.installed.length - beforeInstalled;
171
137
  const newMerged = installer.results.merged.length - beforeMerged;
172
138
  const newSkipped = installer.results.skipped.length - beforeSkipped;
173
139
 
174
- const parts = [];
175
- if (newInstalled > 0) parts.push(colors.success(`${newInstalled} installed`));
176
- if (newMerged > 0) parts.push(colors.blue(`${newMerged} merged`));
177
- if (newSkipped > 0) parts.push(colors.muted(`${newSkipped} skipped`));
178
- const detail = parts.length > 0 ? ` ${colors.dim('—')} ${parts.join(colors.dim(', '))}` : '';
179
-
180
- stepDone(`${icon} ${label}${detail}`);
140
+ if (newMerged > 0 && newInstalled === 0 && newSkipped === 0) {
141
+ spinner.stopAndPersist({
142
+ symbol: colors.blue(icons.merge),
143
+ text: `${label} ${colors.dim('merged')}`,
144
+ });
145
+ } else if (newSkipped > 0 && newInstalled === 0 && newMerged === 0) {
146
+ spinner.stopAndPersist({
147
+ symbol: colors.muted(icons.skip),
148
+ text: `${colors.muted(label)} ${colors.dim('unchanged')}`,
149
+ });
150
+ } else {
151
+ const count = newInstalled + newMerged;
152
+ const detail = count > 0 ? ` ${count} file${count > 1 ? 's' : ''}` : '';
153
+ spinner.stopAndPersist({
154
+ symbol: colors.success(icons.check),
155
+ text: `${label}${colors.dim(detail)}`,
156
+ });
157
+ }
181
158
  } catch (err) {
182
- stepFail(`${icon} ${label} ${colors.dim('—')} ${colors.error(err.message)}`);
159
+ spinner.stopAndPersist({
160
+ symbol: colors.error(icons.cross),
161
+ text: `${label} ${colors.dim(err.message)}`,
162
+ });
183
163
  installer.results.errors.push({ component: componentName, error: err.message });
184
164
  }
185
165
  }
186
166
 
187
- const { installed, skipped, merged, errors } = installer.results;
188
-
189
- // ─── Detailed file list ──────────────────────────────────────────
190
- if (installed.length > 0 || merged.length > 0 || skipped.length > 0) {
191
- separator();
192
- sectionHeader(icons.file, 'File details');
193
- for (const f of installed) fileEntry('install', f.replace(/\\/g, '/'));
194
- for (const m of merged) {
195
- const detail = m.added ? ` (${m.added.length} refs)` : '';
196
- fileEntry('merge', `${m.file.replace(/\\/g, '/')}${detail}`);
197
- }
198
- for (const f of skipped) fileEntry('skip', f.replace(/\\/g, '/'));
199
- for (const e of errors) fileEntry('error', `${(e.file || e.component).replace(/\\/g, '/')}: ${e.error}`);
200
- }
201
-
202
- // ─── Summary ─────────────────────────────────────────────────────
203
- summaryBox({
204
- installed: installed.length,
205
- merged: merged.length,
206
- skipped: skipped.length,
207
- errors: errors.length,
208
- });
167
+ // ─── Summary ────────────────────────────────────────────────
168
+ const { installed, merged, skipped, errors } = installer.results;
209
169
 
210
170
  if (errors.length === 0) {
211
- doneMessage();
171
+ doneMessage({
172
+ installed: installed.length,
173
+ merged: merged.length,
174
+ skipped: skipped.length,
175
+ });
212
176
  } else {
213
- doneWithErrors();
177
+ doneWithErrors({ errors: errors.length });
214
178
  }
215
179
  }
package/src/core/ui.js CHANGED
@@ -1,177 +1,92 @@
1
1
  import chalk from 'chalk';
2
2
 
3
- // ─── Icons ───────────────────────────────────────────────────────────
3
+ // ─── Icons (minimal set) ────────────────────────────────────
4
4
  export const icons = {
5
- ace: '◆',
6
- check: '✔',
7
- cross: '✖',
8
- arrow: '',
9
- arrowR: '',
10
- dot: '',
11
- circle: '',
12
- warn: '⚠',
13
- info: 'ℹ',
14
- skip: '◇',
15
- merge: '⇄',
16
- folder: '▸',
17
- shield: '🛡',
18
- gear: '⚙',
19
- rocket: '🚀',
20
- sparkles: '✨',
21
- package: '📦',
22
- file: '📄',
23
- brain: '🧠',
24
- hook: '🪝',
25
- guard: '🔒',
26
- memory: '💾',
27
- plug: '🔌',
5
+ ace: '◆',
6
+ check: '✔',
7
+ cross: '✖',
8
+ warn: '',
9
+ skip: '',
10
+ dot: '·',
11
+ merge: '~',
28
12
  };
29
13
 
30
- // ─── Colors ──────────────────────────────────────────────────────────
14
+ // ─── Colors ─────────────────────────────────────────────────
31
15
  export const colors = {
32
- brand: chalk.hex('#7C3AED'), // vibrant purple
33
- accent: chalk.hex('#06B6D4'), // cyan
34
- success: chalk.hex('#10B981'), // emerald
35
- warning: chalk.hex('#F59E0B'), // amber
36
- error: chalk.hex('#EF4444'), // red
37
- dim: chalk.hex('#6B7280'), // gray
38
- muted: chalk.hex('#9CA3AF'), // light gray
39
- white: chalk.hex('#F9FAFB'), // near white
40
- blue: chalk.hex('#3B82F6'), // blue
16
+ brand: chalk.hex('#7C3AED'),
17
+ success: chalk.hex('#10B981'),
18
+ warning: chalk.hex('#F59E0B'),
19
+ error: chalk.hex('#EF4444'),
20
+ dim: chalk.hex('#6B7280'),
21
+ muted: chalk.hex('#9CA3AF'),
22
+ white: chalk.hex('#F9FAFB'),
23
+ blue: chalk.hex('#3B82F6'),
41
24
  };
42
25
 
43
- // ─── ASCII Banner ────────────────────────────────────────────────────
44
- export function printBanner(version) {
45
- const purple = colors.brand;
46
- const dim = colors.dim;
26
+ // ─── Screen control ─────────────────────────────────────────
27
+ export function clearScreen() {
28
+ process.stdout.write('\x1B[2J\x1B[3J\x1B[H');
29
+ }
47
30
 
31
+ // ─── Banner (single line) ───────────────────────────────────
32
+ export function printBanner(version) {
48
33
  console.log();
49
- console.log(purple(' ╔═══╗ ╔═══╗ ╔═══╗'));
50
- console.log(purple(' ╠═══╣ ║ ╠═══╝'));
51
- console.log(purple(' ║ ║ ╚═══╝ ╚═══╗'));
52
- console.log();
53
- console.log(` ${purple.bold('ace')} ${dim(`v${version}`)} ${dim('— AI Coding Environment')}`);
34
+ console.log(` ${colors.brand.bold('◆ ace')} ${colors.dim(`v${version}`)}`);
54
35
  console.log();
55
36
  }
56
37
 
57
- // ─── Section Header ──────────────────────────────────────────────────
58
- export function sectionHeader(icon, title) {
59
- console.log(` ${icon} ${colors.white.bold(title)}`);
60
- }
61
-
62
- // ─── Step indicator ──────────────────────────────────────────────────
63
- export function stepStart(label) {
64
- process.stdout.write(` ${colors.dim('│')}\n`);
65
- process.stdout.write(` ${colors.brand(icons.dot)} ${label}\n`);
66
- }
67
-
68
- export function stepDone(label) {
69
- process.stdout.write(` ${colors.success(icons.check)} ${label}\n`);
38
+ // ─── Step-by-step screen (clear + banner + previous answers) ─
39
+ export function renderScreen(version, completedSteps = []) {
40
+ clearScreen();
41
+ printBanner(version);
42
+ for (const step of completedSteps) {
43
+ console.log(step);
44
+ }
45
+ if (completedSteps.length > 0) console.log();
70
46
  }
71
47
 
72
- export function stepSkip(label) {
73
- process.stdout.write(` ${colors.muted(icons.skip)} ${colors.muted(label)}\n`);
48
+ // ─── Step indicators ────────────────────────────────────────
49
+ export function stepDone(label, detail) {
50
+ const d = detail ? colors.dim(` ${detail}`) : '';
51
+ console.log(` ${colors.success(icons.check)} ${label}${d}`);
74
52
  }
75
53
 
76
- export function stepWarn(label) {
77
- process.stdout.write(` ${colors.warning(icons.warn)} ${label}\n`);
54
+ export function stepMerge(label, detail) {
55
+ const d = detail ? colors.dim(` ${detail}`) : '';
56
+ console.log(` ${colors.blue(icons.merge)} ${label}${d}`);
78
57
  }
79
58
 
80
- export function stepFail(label) {
81
- process.stdout.write(` ${colors.error(icons.cross)} ${label}\n`);
59
+ export function stepSkip(label, detail) {
60
+ const d = detail ? colors.dim(` ${detail}`) : '';
61
+ console.log(` ${colors.muted(icons.skip)} ${colors.muted(label)}${d}`);
82
62
  }
83
63
 
84
- // ─── File list (compact) ─────────────────────────────────────────────
85
- export function fileEntry(action, filePath) {
86
- const prefix = {
87
- install: ` ${colors.success('+')}`,
88
- merge: ` ${colors.blue('~')}`,
89
- skip: ` ${colors.muted('-')}`,
90
- overwrite:` ${colors.warning('!')}`,
91
- error: ` ${colors.error('✖')}`,
92
- };
93
- const color = {
94
- install: colors.success,
95
- merge: colors.blue,
96
- skip: colors.muted,
97
- overwrite:colors.warning,
98
- error: colors.error,
99
- };
100
- console.log(`${prefix[action] || ' '} ${(color[action] || colors.dim)(filePath)}`);
64
+ export function stepFail(label, detail) {
65
+ const d = detail ? colors.dim(` ${detail}`) : '';
66
+ console.log(` ${colors.error(icons.cross)} ${label}${d}`);
101
67
  }
102
68
 
103
- // ─── Summary Box ─────────────────────────────────────────────────────
104
- export function summaryBox(stats) {
105
- const { installed, merged, skipped, errors } = stats;
69
+ // ─── Done messages ──────────────────────────────────────────
70
+ export function doneMessage(stats) {
71
+ const parts = [];
72
+ if (stats.installed > 0) parts.push(`${stats.installed} installed`);
73
+ if (stats.merged > 0) parts.push(`${stats.merged} merged`);
74
+ if (stats.skipped > 0) parts.push(`${stats.skipped} skipped`);
106
75
 
107
76
  console.log();
108
- console.log(` ${colors.dim('╭─────────────────────────────────────╮')}`);
109
- console.log(` ${colors.dim('│')} ${colors.white.bold('Installation Summary')} ${colors.dim('│')}`);
110
- console.log(` ${colors.dim('├─────────────────────────────────────┤')}`);
111
-
112
- if (installed > 0) {
113
- console.log(` ${colors.dim('│')} ${colors.success(icons.check)} ${colors.success(`${installed} installed`)}${pad(installed, 'installed')}${colors.dim('│')}`);
114
- }
115
- if (merged > 0) {
116
- console.log(` ${colors.dim('│')} ${colors.blue(icons.merge)} ${colors.blue(`${merged} merged`)}${pad(merged, 'merged')}${colors.dim('│')}`);
117
- }
118
- if (skipped > 0) {
119
- console.log(` ${colors.dim('│')} ${colors.muted(icons.skip)} ${colors.muted(`${skipped} skipped`)}${pad(skipped, 'skipped')}${colors.dim('│')}`);
120
- }
121
- if (errors > 0) {
122
- console.log(` ${colors.dim('│')} ${colors.error(icons.cross)} ${colors.error(`${errors} errors`)}${pad(errors, 'errors')}${colors.dim('│')}`);
123
- }
124
-
125
- console.log(` ${colors.dim('╰─────────────────────────────────────╯')}`);
126
- }
127
-
128
- function pad(count, label) {
129
- const text = `${count} ${label}`;
130
- const total = 33;
131
- const spaces = total - text.length - 4; // 4 = icon + spaces
132
- return ' '.repeat(Math.max(1, spaces));
133
- }
134
-
135
- // ─── Final message ───────────────────────────────────────────────────
136
- export function doneMessage() {
137
- console.log();
138
- console.log(` ${icons.rocket} ${colors.success.bold('Your AI coding environment is ready.')}`);
139
- console.log(` ${colors.dim(`Run ${chalk.white('ace doctor')} to verify the installation.`)}`);
77
+ console.log(` ${colors.success(icons.check)} ${colors.success.bold('Done.')} ${colors.dim(parts.join(', '))}`);
78
+ console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to verify.`)}`);
140
79
  console.log();
141
80
  }
142
81
 
143
- export function doneWithErrors() {
144
- console.log();
145
- console.log(` ${icons.warn} ${colors.warning.bold('Completed with errors.')}`);
146
- console.log(` ${colors.dim(`Run ${chalk.white('ace doctor')} to diagnose.`)}`);
82
+ export function doneWithErrors(stats) {
147
83
  console.log();
148
- }
149
-
150
- // ─── Separator ───────────────────────────────────────────────────────
151
- export function separator() {
152
- console.log(` ${colors.dim('│')}`);
153
- }
154
-
155
- // ─── Conflict prompt helpers ─────────────────────────────────────────
156
- export function conflictHeader(componentName, icon, fileCount) {
84
+ console.log(` ${colors.warning(icons.warn)} ${colors.warning.bold('Done with errors.')} ${colors.dim(`${stats.errors} failed`)}`);
85
+ console.log(` ${colors.dim(` Run ${chalk.white('ace doctor')} to diagnose.`)}`);
157
86
  console.log();
158
- console.log(` ${colors.warning(icons.warn)} ${icon} ${colors.white.bold(componentName)} — ${colors.warning(`${fileCount} file(s) already exist`)}`);
159
87
  }
160
88
 
161
- export function conflictFile(filePath) {
162
- console.log(` ${colors.dim(icons.arrowR)} ${colors.muted(filePath)}`);
163
- }
164
-
165
- // ─── Component icons ─────────────────────────────────────────────────
166
- export const componentIcons = {
167
- core: icons.gear,
168
- rules: icons.brain,
169
- plugin: icons.plug,
170
- hooks: icons.hook,
171
- hookify: icons.guard,
172
- memory: icons.memory,
173
- };
174
-
89
+ // ─── Component labels ───────────────────────────────────────
175
90
  export const componentLabels = {
176
91
  core: 'Core Config',
177
92
  rules: 'Rules',