claude-autopm 1.18.0 → 1.20.1

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 (108) hide show
  1. package/README.md +159 -0
  2. package/autopm/.claude/agents/README.md +1 -1
  3. package/autopm/.claude/agents/core/mcp-manager.md +1 -1
  4. package/autopm/.claude/agents/decision-matrices/python-backend-selection.md +25 -25
  5. package/autopm/.claude/agents/decision-matrices/ui-framework-selection.md +43 -43
  6. package/autopm/.claude/agents/devops/github-operations-specialist.md +1 -1
  7. package/autopm/.claude/agents/frameworks/README.md +5 -5
  8. package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -1
  9. package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -1
  10. package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -1
  11. package/autopm/.claude/agents/frameworks/react-ui-expert.md +3 -3
  12. package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +3 -3
  13. package/autopm/.claude/agents/frameworks/ux-design-expert.md +3 -3
  14. package/autopm/.claude/commands/infrastructure/traefik-setup.md +1 -1
  15. package/autopm/.claude/commands/playwright/test-scaffold.md +1 -1
  16. package/autopm/.claude/commands/pm/context.md +11 -0
  17. package/autopm/.claude/commands/pm/epic-decompose.md +25 -2
  18. package/autopm/.claude/commands/pm/epic-oneshot.md +13 -0
  19. package/autopm/.claude/commands/pm/epic-start.md +19 -0
  20. package/autopm/.claude/commands/pm/epic-sync-modular.md +10 -10
  21. package/autopm/.claude/commands/pm/epic-sync.md +14 -14
  22. package/autopm/.claude/commands/pm/issue-start.md +50 -5
  23. package/autopm/.claude/commands/pm/issue-sync.md +15 -15
  24. package/autopm/.claude/commands/pm/what-next.md +11 -0
  25. package/autopm/.claude/commands/ui/bootstrap-scaffold.md +6 -5
  26. package/autopm/.claude/commands/ui/tailwind-system.md +1 -1
  27. package/autopm/.claude/examples/mcp/playwright-mcp.md +2 -2
  28. package/autopm/.claude/examples/mcp-servers.example.json +2 -2
  29. package/autopm/.claude/hooks/docker-first-enforcement.sh +1 -1
  30. package/autopm/.claude/mcp/MCP-REGISTRY.md +1 -1
  31. package/autopm/.claude/mcp/playwright-mcp.md +2 -2
  32. package/autopm/.claude/rules/agent-coordination.md +26 -24
  33. package/autopm/.claude/rules/docker-first-development.md +1 -1
  34. package/autopm/.claude/rules/infrastructure-pipeline.md +1 -1
  35. package/autopm/.claude/rules/ui-development-standards.md +1 -1
  36. package/autopm/.claude/rules/visual-testing.md +3 -3
  37. package/autopm/.claude/scripts/azure/active-work.js +2 -2
  38. package/autopm/.claude/scripts/azure/blocked.js +13 -13
  39. package/autopm/.claude/scripts/azure/daily.js +1 -1
  40. package/autopm/.claude/scripts/azure/dashboard.js +1 -1
  41. package/autopm/.claude/scripts/azure/feature-list.js +2 -2
  42. package/autopm/.claude/scripts/azure/feature-status.js +1 -1
  43. package/autopm/.claude/scripts/azure/next-task.js +1 -1
  44. package/autopm/.claude/scripts/azure/search.js +1 -1
  45. package/autopm/.claude/scripts/azure/setup.js +15 -15
  46. package/autopm/.claude/scripts/azure/sprint-report.js +2 -2
  47. package/autopm/.claude/scripts/azure/sync.js +1 -1
  48. package/autopm/.claude/scripts/azure/us-list.js +1 -1
  49. package/autopm/.claude/scripts/azure/us-status.js +1 -1
  50. package/autopm/.claude/scripts/azure/validate.js +13 -13
  51. package/autopm/.claude/scripts/lib/frontmatter-utils.sh +42 -7
  52. package/autopm/.claude/scripts/lib/logging-utils.sh +20 -16
  53. package/autopm/.claude/scripts/lib/validation-utils.sh +1 -1
  54. package/autopm/.claude/scripts/pm/context.js +338 -0
  55. package/autopm/.claude/scripts/pm/issue-sync/format-comment.sh +3 -3
  56. package/autopm/.claude/scripts/pm/lib/README.md +85 -0
  57. package/autopm/.claude/scripts/pm/lib/logger.js +78 -0
  58. package/autopm/.claude/scripts/pm/next.js +25 -1
  59. package/autopm/.claude/scripts/pm/what-next.js +660 -0
  60. package/autopm/.claude/teams.json +3 -5
  61. package/autopm/.claude/templates/claude-templates/addons/devops-agents.md +2 -2
  62. package/autopm/.claude/templates/claude-templates/addons/docker-agents.md +4 -4
  63. package/autopm/.claude/templates/claude-templates/addons/minimal-agents.md +1 -1
  64. package/autopm/.claude/templates/issue-decomposition/api.yaml +2 -2
  65. package/autopm/.claude/templates/issue-decomposition/auth.yaml +4 -4
  66. package/autopm/.claude/templates/issue-decomposition/crud.yaml +3 -3
  67. package/autopm/.claude/templates/issue-decomposition/default.yaml +1 -1
  68. package/autopm/.claude/templates/issue-decomposition/ui-feature.yaml +2 -2
  69. package/bin/autopm.js +25 -0
  70. package/package.json +1 -2
  71. package/lib/agentExecutor.js.deprecated +0 -101
  72. package/lib/azure/cache.js +0 -80
  73. package/lib/azure/client.js +0 -77
  74. package/lib/azure/formatter.js +0 -177
  75. package/lib/commandHelpers.js +0 -177
  76. package/lib/context/manager.js +0 -290
  77. package/lib/documentation/manager.js +0 -528
  78. package/lib/github/workflow-manager.js +0 -546
  79. package/lib/helpers/azure-batch-api.js +0 -133
  80. package/lib/helpers/azure-cache-manager.js +0 -287
  81. package/lib/helpers/azure-parallel-processor.js +0 -158
  82. package/lib/helpers/azure-work-item-create.js +0 -278
  83. package/lib/helpers/gh-issue-create.js +0 -250
  84. package/lib/helpers/interactive-prompt.js +0 -336
  85. package/lib/helpers/output-manager.js +0 -335
  86. package/lib/helpers/progress-indicator.js +0 -258
  87. package/lib/performance/benchmarker.js +0 -429
  88. package/lib/pm/epic-decomposer.js +0 -273
  89. package/lib/pm/epic-syncer.js +0 -221
  90. package/lib/prdMetadata.js +0 -270
  91. package/lib/providers/azure/index.js +0 -234
  92. package/lib/providers/factory.js +0 -87
  93. package/lib/providers/github/index.js +0 -204
  94. package/lib/providers/interface.js +0 -73
  95. package/lib/python/scaffold-manager.js +0 -576
  96. package/lib/react/scaffold-manager.js +0 -745
  97. package/lib/regression/analyzer.js +0 -578
  98. package/lib/release/manager.js +0 -324
  99. package/lib/tailwind/manager.js +0 -486
  100. package/lib/traefik/manager.js +0 -484
  101. package/lib/utils/colors.js +0 -126
  102. package/lib/utils/config.js +0 -317
  103. package/lib/utils/filesystem.js +0 -316
  104. package/lib/utils/logger.js +0 -135
  105. package/lib/utils/prompts.js +0 -294
  106. package/lib/utils/shell.js +0 -237
  107. package/lib/validators/email-validator.js +0 -337
  108. package/lib/workflow/manager.js +0 -449
@@ -1,484 +0,0 @@
1
- /**
2
- * Traefik Configuration Manager
3
- * Centralized Traefik setup and configuration functionality
4
- */
5
-
6
- const fs = require('fs').promises;
7
- const path = require('path');
8
- const { exec } = require('child_process');
9
- const { promisify } = require('util');
10
- const execAsync = promisify(exec);
11
-
12
- /**
13
- * Configuration constants
14
- */
15
- const CONFIG = {
16
- directories: {
17
- traefik: '.claude/traefik'
18
- },
19
- defaults: {
20
- traefikImage: 'traefik:v2.9',
21
- network: 'web',
22
- dashboardPort: 8080,
23
- httpPort: 80,
24
- httpsPort: 443
25
- },
26
- files: {
27
- config: 'traefik.yml',
28
- dockerCompose: 'docker-compose.yml',
29
- routes: 'routes.json'
30
- }
31
- };
32
-
33
- /**
34
- * Default configurations
35
- */
36
- const DEFAULT_CONFIGS = {
37
- traefik: {
38
- api: {
39
- dashboard: true,
40
- debug: false
41
- },
42
- entryPoints: {
43
- web: {
44
- address: ':80'
45
- },
46
- websecure: {
47
- address: ':443'
48
- }
49
- },
50
- providers: {
51
- docker: {
52
- endpoint: 'unix:///var/run/docker.sock',
53
- exposedByDefault: false
54
- },
55
- file: {
56
- directory: '/etc/traefik/dynamic',
57
- watch: true
58
- }
59
- },
60
- log: {
61
- level: 'INFO'
62
- },
63
- accessLog: {}
64
- },
65
- routes: {
66
- routes: [
67
- {
68
- name: 'api',
69
- rule: 'PathPrefix(`/api`)',
70
- service: 'api-service',
71
- servers: [{ url: 'http://api:3000' }]
72
- },
73
- {
74
- name: 'web',
75
- rule: 'Host(`localhost`)',
76
- service: 'web-service',
77
- servers: [{ url: 'http://web:80' }]
78
- }
79
- ]
80
- }
81
- };
82
-
83
- class TraefikManager {
84
- constructor(projectRoot = process.cwd()) {
85
- this.projectRoot = projectRoot;
86
- this.traefikDir = path.join(projectRoot, CONFIG.directories.traefik);
87
- }
88
-
89
- /**
90
- * Generates Traefik configuration
91
- */
92
- async generateConfig(options = {}) {
93
- await fs.mkdir(this.traefikDir, { recursive: true });
94
-
95
- const config = {
96
- ...DEFAULT_CONFIGS.traefik,
97
- api: {
98
- ...DEFAULT_CONFIGS.traefik.api,
99
- debug: options.debug || false
100
- },
101
- log: {
102
- level: options.logLevel || 'INFO'
103
- }
104
- };
105
-
106
- const configPath = path.join(this.traefikDir, CONFIG.files.config);
107
- const yaml = this.generateYAML(config);
108
- await fs.writeFile(configPath, yaml);
109
-
110
- return {
111
- path: configPath,
112
- config: config
113
- };
114
- }
115
-
116
- /**
117
- * Generates Docker Compose configuration
118
- */
119
- async generateDockerCompose(options = {}) {
120
- await fs.mkdir(this.traefikDir, { recursive: true });
121
-
122
- const dockerCompose = {
123
- version: '3.8',
124
- services: {
125
- traefik: {
126
- image: options.image || CONFIG.defaults.traefikImage,
127
- container_name: 'traefik',
128
- restart: 'unless-stopped',
129
- ports: [
130
- `${CONFIG.defaults.httpPort}:80`,
131
- `${CONFIG.defaults.httpsPort}:443`,
132
- `${CONFIG.defaults.dashboardPort}:8080`
133
- ],
134
- volumes: [
135
- '/var/run/docker.sock:/var/run/docker.sock:ro',
136
- './traefik.yml:/traefik.yml:ro',
137
- './dynamic:/etc/traefik/dynamic:ro',
138
- './letsencrypt:/letsencrypt'
139
- ],
140
- networks: [CONFIG.defaults.network],
141
- labels: {
142
- 'traefik.enable': 'true',
143
- 'traefik.http.routers.dashboard.rule': 'Host(`traefik.localhost`)',
144
- 'traefik.http.routers.dashboard.service': 'api@internal',
145
- 'traefik.http.routers.dashboard.middlewares': 'auth'
146
- }
147
- }
148
- },
149
- networks: {
150
- [CONFIG.defaults.network]: {
151
- external: true
152
- }
153
- }
154
- };
155
-
156
- const dockerPath = path.join(this.traefikDir, CONFIG.files.dockerCompose);
157
- const yaml = this.generateYAML(dockerCompose);
158
- await fs.writeFile(dockerPath, yaml);
159
-
160
- return {
161
- path: dockerPath,
162
- config: dockerCompose
163
- };
164
- }
165
-
166
- /**
167
- * Configures routes
168
- */
169
- async configureRoutes(customRoutes = null) {
170
- await fs.mkdir(this.traefikDir, { recursive: true });
171
- const routesPath = path.join(this.traefikDir, CONFIG.files.routes);
172
-
173
- let routesConfig;
174
- if (customRoutes) {
175
- routesConfig = customRoutes;
176
- } else {
177
- try {
178
- const content = await fs.readFile(routesPath, 'utf8');
179
- routesConfig = JSON.parse(content);
180
- } catch (error) {
181
- routesConfig = DEFAULT_CONFIGS.routes;
182
- await fs.writeFile(routesPath, JSON.stringify(routesConfig, null, 2));
183
- }
184
- }
185
-
186
- // Generate dynamic configuration for each route
187
- for (const route of routesConfig.routes) {
188
- const dynamicConfig = {
189
- http: {
190
- routers: {
191
- [route.name]: {
192
- rule: route.rule,
193
- service: route.service,
194
- middlewares: route.middlewares || []
195
- }
196
- },
197
- services: {
198
- [route.service]: {
199
- loadBalancer: {
200
- servers: route.servers || [{ url: 'http://localhost:3000' }]
201
- }
202
- }
203
- }
204
- }
205
- };
206
-
207
- const dynamicPath = path.join(this.traefikDir, `dynamic-${route.name}.yml`);
208
- await fs.writeFile(dynamicPath, this.generateYAML(dynamicConfig));
209
- }
210
-
211
- return {
212
- routes: routesConfig.routes,
213
- path: routesPath
214
- };
215
- }
216
-
217
- /**
218
- * Configures SSL/TLS
219
- */
220
- async configureSSL(domain) {
221
- await fs.mkdir(this.traefikDir, { recursive: true });
222
-
223
- const sslConfig = {
224
- tls: {
225
- certificates: [
226
- {
227
- certFile: `/letsencrypt/certs/${domain}.crt`,
228
- keyFile: `/letsencrypt/certs/${domain}.key`
229
- }
230
- ],
231
- stores: {
232
- default: {
233
- defaultCertificate: {
234
- certFile: `/letsencrypt/certs/${domain}.crt`,
235
- keyFile: `/letsencrypt/certs/${domain}.key`
236
- }
237
- }
238
- }
239
- }
240
- };
241
-
242
- const sslPath = path.join(this.traefikDir, 'ssl-config.yml');
243
- await fs.writeFile(sslPath, this.generateYAML(sslConfig));
244
-
245
- return {
246
- path: sslPath,
247
- domain: domain,
248
- config: sslConfig
249
- };
250
- }
251
-
252
- /**
253
- * Configures Let's Encrypt
254
- */
255
- async configureLetsEncrypt(email, staging = false) {
256
- await fs.mkdir(this.traefikDir, { recursive: true });
257
-
258
- const acmeConfig = {
259
- certificatesResolvers: {
260
- letsencrypt: {
261
- acme: {
262
- email: email,
263
- storage: '/letsencrypt/acme.json',
264
- httpChallenge: {
265
- entryPoint: 'web'
266
- },
267
- caServer: staging
268
- ? 'https://acme-staging-v02.api.letsencrypt.org/directory'
269
- : 'https://acme-v02.api.letsencrypt.org/directory'
270
- }
271
- }
272
- }
273
- };
274
-
275
- const acmePath = path.join(this.traefikDir, 'letsencrypt-config.yml');
276
- await fs.writeFile(acmePath, this.generateYAML(acmeConfig));
277
-
278
- return {
279
- path: acmePath,
280
- email: email,
281
- staging: staging,
282
- config: acmeConfig
283
- };
284
- }
285
-
286
- /**
287
- * Configures middleware
288
- */
289
- async configureMiddleware(type, options = {}) {
290
- await fs.mkdir(this.traefikDir, { recursive: true });
291
-
292
- const middlewares = {
293
- http: {
294
- middlewares: {}
295
- }
296
- };
297
-
298
- switch (type) {
299
- case 'auth':
300
- middlewares.http.middlewares.auth = {
301
- basicAuth: {
302
- users: options.users || ['admin:$2y$10$...']
303
- }
304
- };
305
- break;
306
-
307
- case 'compress':
308
- middlewares.http.middlewares.compress = {
309
- compress: {}
310
- };
311
- break;
312
-
313
- case 'redirect':
314
- middlewares.http.middlewares['redirect-to-https'] = {
315
- redirectScheme: {
316
- scheme: 'https',
317
- permanent: true
318
- }
319
- };
320
- break;
321
-
322
- case 'ratelimit':
323
- middlewares.http.middlewares['rate-limit'] = {
324
- rateLimit: {
325
- average: options.average || 100,
326
- burst: options.burst || 200,
327
- period: '1m'
328
- }
329
- };
330
- break;
331
-
332
- default:
333
- throw new Error(`Unknown middleware type: ${type}`);
334
- }
335
-
336
- const middlewarePath = path.join(this.traefikDir, `middleware-${type}.yml`);
337
- await fs.writeFile(middlewarePath, this.generateYAML(middlewares));
338
-
339
- return {
340
- path: middlewarePath,
341
- type: type,
342
- config: middlewares
343
- };
344
- }
345
-
346
- /**
347
- * Configures service discovery
348
- */
349
- async configureDiscovery(provider, options = {}) {
350
- await fs.mkdir(this.traefikDir, { recursive: true });
351
-
352
- let discoveryConfig = {};
353
-
354
- switch (provider) {
355
- case 'docker':
356
- discoveryConfig = {
357
- providers: {
358
- docker: {
359
- endpoint: 'unix:///var/run/docker.sock',
360
- exposedByDefault: false,
361
- network: options.network || CONFIG.defaults.network,
362
- watch: true
363
- }
364
- }
365
- };
366
- break;
367
-
368
- case 'kubernetes':
369
- case 'k8s':
370
- discoveryConfig = {
371
- providers: {
372
- kubernetes: {
373
- namespace: options.namespace || 'default',
374
- labelSelector: options.selector || '',
375
- ingressClass: 'traefik'
376
- }
377
- }
378
- };
379
- break;
380
-
381
- case 'consul':
382
- discoveryConfig = {
383
- providers: {
384
- consul: {
385
- endpoint: options.endpoint || 'http://localhost:8500',
386
- prefix: 'traefik',
387
- watch: true
388
- }
389
- }
390
- };
391
- break;
392
-
393
- default:
394
- throw new Error(`Unknown provider: ${provider}`);
395
- }
396
-
397
- const discoveryPath = path.join(this.traefikDir, `discovery-${provider}.yml`);
398
- await fs.writeFile(discoveryPath, this.generateYAML(discoveryConfig));
399
-
400
- return {
401
- path: discoveryPath,
402
- provider: provider,
403
- config: discoveryConfig
404
- };
405
- }
406
-
407
- /**
408
- * Checks Traefik status
409
- */
410
- async checkStatus() {
411
- const status = {
412
- configuration: {
413
- exists: false,
414
- files: []
415
- },
416
- container: {
417
- running: false,
418
- status: 'unknown'
419
- },
420
- dashboard: {
421
- url: `http://localhost:${CONFIG.defaults.dashboardPort}/dashboard`
422
- }
423
- };
424
-
425
- // Check configuration files
426
- try {
427
- const files = await fs.readdir(this.traefikDir);
428
- status.configuration.exists = true;
429
- for (const file of files) {
430
- const stats = await fs.stat(path.join(this.traefikDir, file));
431
- status.configuration.files.push({
432
- name: file,
433
- size: stats.size
434
- });
435
- }
436
- } catch (error) {
437
- // No configuration directory
438
- }
439
-
440
- // Check Docker container status
441
- try {
442
- const { stdout } = await execAsync('docker ps --filter name=traefik --format "{{.Status}}"');
443
- if (stdout.includes('Up')) {
444
- status.container.running = true;
445
- status.container.status = stdout.trim();
446
- }
447
- } catch (error) {
448
- // Docker not available or container not running
449
- }
450
-
451
- return status;
452
- }
453
-
454
- /**
455
- * Simple YAML generator
456
- */
457
- generateYAML(obj, indent = 0) {
458
- let yaml = '';
459
- const spaces = ' '.repeat(indent);
460
-
461
- for (const [key, value] of Object.entries(obj)) {
462
- if (value === null || value === undefined) {
463
- yaml += `${spaces}${key}:\n`;
464
- } else if (typeof value === 'object' && !Array.isArray(value)) {
465
- yaml += `${spaces}${key}:\n${this.generateYAML(value, indent + 1)}`;
466
- } else if (Array.isArray(value)) {
467
- yaml += `${spaces}${key}:\n`;
468
- for (const item of value) {
469
- if (typeof item === 'object') {
470
- yaml += `${spaces} -\n${this.generateYAML(item, indent + 2)}`;
471
- } else {
472
- yaml += `${spaces} - ${item}\n`;
473
- }
474
- }
475
- } else {
476
- yaml += `${spaces}${key}: ${value}\n`;
477
- }
478
- }
479
-
480
- return yaml;
481
- }
482
- }
483
-
484
- module.exports = TraefikManager;
@@ -1,126 +0,0 @@
1
- /**
2
- * Cross-platform color utility
3
- * Works with or without chalk library
4
- */
5
-
6
- // Color codes for fallback
7
- const colors = {
8
- reset: '\x1b[0m',
9
- bold: '\x1b[1m',
10
- underline: '\x1b[4m',
11
-
12
- // Foreground colors
13
- black: '\x1b[30m',
14
- red: '\x1b[31m',
15
- green: '\x1b[32m',
16
- yellow: '\x1b[33m',
17
- blue: '\x1b[34m',
18
- magenta: '\x1b[35m',
19
- cyan: '\x1b[36m',
20
- white: '\x1b[37m',
21
- gray: '\x1b[90m',
22
- grey: '\x1b[90m',
23
-
24
- // Background colors
25
- bgBlack: '\x1b[40m',
26
- bgRed: '\x1b[41m',
27
- bgGreen: '\x1b[42m',
28
- bgYellow: '\x1b[43m',
29
- bgBlue: '\x1b[44m',
30
- bgMagenta: '\x1b[45m',
31
- bgCyan: '\x1b[46m',
32
- bgWhite: '\x1b[47m'
33
- };
34
-
35
- class ColorHelper {
36
- constructor() {
37
- this.supportsColor = process.stdout.isTTY &&
38
- process.env.TERM !== 'dumb' &&
39
- !process.env.NO_COLOR;
40
- }
41
-
42
- colorize(text, color) {
43
- if (!this.supportsColor) {
44
- return text;
45
- }
46
-
47
- const code = colors[color] || '';
48
- return code + text + colors.reset;
49
- }
50
-
51
- // Basic colors
52
- red(text) {
53
- return this.colorize(text, 'red');
54
- }
55
-
56
- green(text) {
57
- return this.colorize(text, 'green');
58
- }
59
-
60
- yellow(text) {
61
- return this.colorize(text, 'yellow');
62
- }
63
-
64
- blue(text) {
65
- return this.colorize(text, 'blue');
66
- }
67
-
68
- magenta(text) {
69
- return this.colorize(text, 'magenta');
70
- }
71
-
72
- cyan(text) {
73
- return this.colorize(text, 'cyan');
74
- }
75
-
76
- white(text) {
77
- return this.colorize(text, 'white');
78
- }
79
-
80
- gray(text) {
81
- return this.colorize(text, 'gray');
82
- }
83
-
84
- grey(text) {
85
- return this.colorize(text, 'grey');
86
- }
87
-
88
- // Styles
89
- bold(text) {
90
- return this.colorize(text, 'bold');
91
- }
92
-
93
- underline(text) {
94
- return this.colorize(text, 'underline');
95
- }
96
-
97
- // Combined styles
98
- boldRed(text) {
99
- return this.bold(this.red(text));
100
- }
101
-
102
- boldGreen(text) {
103
- return this.bold(this.green(text));
104
- }
105
-
106
- boldYellow(text) {
107
- return this.bold(this.yellow(text));
108
- }
109
-
110
- boldBlue(text) {
111
- return this.bold(this.blue(text));
112
- }
113
-
114
- boldCyan(text) {
115
- return this.bold(this.cyan(text));
116
- }
117
-
118
- boldUnderline(text) {
119
- return this.bold(this.underline(text));
120
- }
121
-
122
- // Note: Removed chaining support due to infinite recursion issues
123
- // Use direct method calls instead (e.g., boldRed(), boldGreen(), etc.)
124
- }
125
-
126
- module.exports = new ColorHelper();