wiggum-cli 0.5.4 → 0.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.
Files changed (89) hide show
  1. package/README.md +88 -22
  2. package/dist/ai/conversation/conversation-manager.d.ts +84 -0
  3. package/dist/ai/conversation/conversation-manager.d.ts.map +1 -0
  4. package/dist/ai/conversation/conversation-manager.js +159 -0
  5. package/dist/ai/conversation/conversation-manager.js.map +1 -0
  6. package/dist/ai/conversation/index.d.ts +8 -0
  7. package/dist/ai/conversation/index.d.ts.map +1 -0
  8. package/dist/ai/conversation/index.js +8 -0
  9. package/dist/ai/conversation/index.js.map +1 -0
  10. package/dist/ai/conversation/spec-generator.d.ts +62 -0
  11. package/dist/ai/conversation/spec-generator.d.ts.map +1 -0
  12. package/dist/ai/conversation/spec-generator.js +267 -0
  13. package/dist/ai/conversation/spec-generator.js.map +1 -0
  14. package/dist/ai/conversation/url-fetcher.d.ts +26 -0
  15. package/dist/ai/conversation/url-fetcher.d.ts.map +1 -0
  16. package/dist/ai/conversation/url-fetcher.js +145 -0
  17. package/dist/ai/conversation/url-fetcher.js.map +1 -0
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +44 -34
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/init.d.ts +19 -0
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/init.js +61 -21
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/commands/new.d.ts +11 -1
  26. package/dist/commands/new.d.ts.map +1 -1
  27. package/dist/commands/new.js +102 -43
  28. package/dist/commands/new.js.map +1 -1
  29. package/dist/commands/run.js +3 -3
  30. package/dist/commands/run.js.map +1 -1
  31. package/dist/generator/config.d.ts +3 -3
  32. package/dist/generator/config.d.ts.map +1 -1
  33. package/dist/generator/config.js +5 -3
  34. package/dist/generator/config.js.map +1 -1
  35. package/dist/generator/index.js +1 -1
  36. package/dist/generator/index.js.map +1 -1
  37. package/dist/generator/writer.js +1 -1
  38. package/dist/generator/writer.js.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +34 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/repl/command-parser.d.ts +84 -0
  44. package/dist/repl/command-parser.d.ts.map +1 -0
  45. package/dist/repl/command-parser.js +112 -0
  46. package/dist/repl/command-parser.js.map +1 -0
  47. package/dist/repl/index.d.ts +8 -0
  48. package/dist/repl/index.d.ts.map +1 -0
  49. package/dist/repl/index.js +8 -0
  50. package/dist/repl/index.js.map +1 -0
  51. package/dist/repl/repl-loop.d.ts +30 -0
  52. package/dist/repl/repl-loop.d.ts.map +1 -0
  53. package/dist/repl/repl-loop.js +262 -0
  54. package/dist/repl/repl-loop.js.map +1 -0
  55. package/dist/repl/session-state.d.ts +37 -0
  56. package/dist/repl/session-state.d.ts.map +1 -0
  57. package/dist/repl/session-state.js +26 -0
  58. package/dist/repl/session-state.js.map +1 -0
  59. package/dist/templates/root/README.md.tmpl +1 -1
  60. package/dist/templates/scripts/feature-loop.sh.tmpl +17 -17
  61. package/dist/templates/scripts/loop.sh.tmpl +7 -7
  62. package/dist/templates/scripts/ralph-monitor.sh.tmpl +5 -5
  63. package/dist/utils/config.d.ts +7 -7
  64. package/dist/utils/config.js +4 -4
  65. package/dist/utils/config.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/ai/conversation/conversation-manager.ts +230 -0
  68. package/src/ai/conversation/index.ts +23 -0
  69. package/src/ai/conversation/spec-generator.ts +327 -0
  70. package/src/ai/conversation/url-fetcher.ts +180 -0
  71. package/src/cli.ts +47 -34
  72. package/src/commands/init.ts +86 -22
  73. package/src/commands/new.ts +121 -44
  74. package/src/commands/run.ts +3 -3
  75. package/src/generator/config.ts +5 -3
  76. package/src/generator/index.ts +1 -1
  77. package/src/generator/writer.ts +1 -1
  78. package/src/index.ts +46 -0
  79. package/src/repl/command-parser.ts +154 -0
  80. package/src/repl/index.ts +23 -0
  81. package/src/repl/repl-loop.ts +339 -0
  82. package/src/repl/session-state.ts +63 -0
  83. package/src/templates/config/ralph.config.cjs.tmpl +38 -0
  84. package/src/templates/root/README.md.tmpl +1 -1
  85. package/src/templates/scripts/feature-loop.sh.tmpl +17 -17
  86. package/src/templates/scripts/loop.sh.tmpl +7 -7
  87. package/src/templates/scripts/ralph-monitor.sh.tmpl +5 -5
  88. package/src/utils/config.ts +9 -9
  89. /package/{src/templates/config/ralph.config.js.tmpl → dist/templates/config/ralph.config.cjs.tmpl} +0 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * New Command
3
- * Create a new feature specification from template
3
+ * Create a new feature specification from template or AI interview
4
4
  */
5
5
 
6
6
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
@@ -9,6 +9,9 @@ import { fileURLToPath } from 'node:url';
9
9
  import { spawn } from 'node:child_process';
10
10
  import { logger } from '../utils/logger.js';
11
11
  import { loadConfigWithDefaults, hasConfig } from '../utils/config.js';
12
+ import { getAvailableProvider, type AIProvider, AVAILABLE_MODELS } from '../ai/providers.js';
13
+ import { SpecGenerator } from '../ai/conversation/index.js';
14
+ import { Scanner, type ScanResult } from '../scanner/index.js';
12
15
  import pc from 'picocolors';
13
16
  import * as prompts from '@clack/prompts';
14
17
 
@@ -21,6 +24,14 @@ export interface NewOptions {
21
24
  yes?: boolean;
22
25
  /** Force overwrite if file exists */
23
26
  force?: boolean;
27
+ /** Use AI interview to generate spec */
28
+ ai?: boolean;
29
+ /** AI provider (anthropic, openai, openrouter) */
30
+ provider?: AIProvider;
31
+ /** Model to use for AI generation */
32
+ model?: string;
33
+ /** Pre-loaded scan result (from REPL session) */
34
+ scanResult?: ScanResult;
24
35
  }
25
36
 
26
37
  /**
@@ -97,6 +108,15 @@ Describe what this feature does and why it's needed.
97
108
  - [ ] Question 2 - Clarification required
98
109
  `;
99
110
 
111
+ /**
112
+ * Get default model for a provider
113
+ */
114
+ function getDefaultModelForProvider(provider: AIProvider): string {
115
+ const models = AVAILABLE_MODELS[provider];
116
+ const recommended = models.find(m => m.hint?.includes('recommended'));
117
+ return recommended?.value || models[0].value;
118
+ }
119
+
100
120
  /**
101
121
  * Find the _example.md template
102
122
  */
@@ -178,7 +198,7 @@ export async function newCommand(feature: string, options: NewOptions = {}): Pro
178
198
  // Sanitize feature name (allow alphanumeric, hyphens, underscores)
179
199
  if (!/^[a-zA-Z0-9_-]+$/.test(feature)) {
180
200
  logger.error('Feature name must contain only letters, numbers, hyphens, and underscores');
181
- logger.info('Example: ralph new my-feature or ralph new user_auth');
201
+ logger.info('Example: wiggum new my-feature or wiggum new user_auth');
182
202
  process.exit(1);
183
203
  }
184
204
 
@@ -194,7 +214,7 @@ export async function newCommand(feature: string, options: NewOptions = {}): Pro
194
214
 
195
215
  // Check for config
196
216
  if (!hasConfig(projectRoot)) {
197
- logger.warn('No ralph.config.js found. Run "ralph init" first to configure your project.');
217
+ logger.warn('No ralph.config.cjs found. Run "wiggum init" first to configure your project.');
198
218
  logger.info('Using default paths...');
199
219
  console.log('');
200
220
  }
@@ -234,55 +254,112 @@ export async function newCommand(feature: string, options: NewOptions = {}): Pro
234
254
  logger.warn('Overwriting existing spec file');
235
255
  }
236
256
 
237
- // Find or use default template
238
- let templateContent: string;
257
+ // Determine if we should use AI generation
258
+ const provider = options.provider || getAvailableProvider();
259
+ const useAi = options.ai && provider !== null;
239
260
 
240
- // Try to find _example.md template
241
- const exampleTemplate = await findExampleTemplate(projectRoot);
242
- if (exampleTemplate) {
243
- logger.info(`Using template: ${exampleTemplate}`);
244
- templateContent = readFileSync(exampleTemplate, 'utf-8');
245
- } else {
246
- // Try package template
247
- const packageTemplateDir = getPackageTemplateDir();
248
- const packageTemplate = join(packageTemplateDir, '_example.md.tmpl');
249
- if (existsSync(packageTemplate)) {
250
- logger.info(`Using package template`);
251
- templateContent = readFileSync(packageTemplate, 'utf-8');
252
- } else {
253
- // Use default template
254
- logger.info('Using default template');
255
- templateContent = DEFAULT_SPEC_TEMPLATE;
256
- }
257
- }
261
+ let specContent: string;
258
262
 
259
- // Process template
260
- const specContent = processTemplate(templateContent, feature);
263
+ if (useAi && provider) {
264
+ // Use AI-powered spec generation
265
+ const model = options.model || getDefaultModelForProvider(provider);
266
+ logger.info(`Using AI spec generation (${provider}/${model})`);
261
267
 
262
- // Confirm with user (unless --yes)
263
- if (!options.yes) {
264
- console.log('');
265
- console.log(pc.cyan('--- Spec Preview ---'));
266
- console.log(`File: ${specPath}`);
267
- console.log('');
268
-
269
- // Show first few lines of the processed template
270
- const previewLines = specContent.split('\n').slice(0, 15);
271
- console.log(pc.dim(previewLines.join('\n')));
272
- if (specContent.split('\n').length > 15) {
273
- console.log(pc.dim('...'));
268
+ // Get or perform scan
269
+ let scanResult = options.scanResult;
270
+ if (!scanResult) {
271
+ const scanner = new Scanner();
272
+ scanResult = await scanner.scan(projectRoot);
274
273
  }
275
- console.log('');
276
274
 
277
- const shouldCreate = await prompts.confirm({
278
- message: 'Create this spec file?',
279
- initialValue: true,
275
+ const specGenerator = new SpecGenerator({
276
+ featureName: feature,
277
+ projectRoot,
278
+ provider,
279
+ model,
280
+ scanResult,
280
281
  });
281
282
 
282
- if (prompts.isCancel(shouldCreate) || !shouldCreate) {
283
- logger.info('Cancelled');
283
+ const generatedSpec = await specGenerator.run();
284
+
285
+ if (!generatedSpec) {
286
+ logger.info('Spec generation cancelled');
284
287
  return;
285
288
  }
289
+
290
+ specContent = generatedSpec;
291
+
292
+ // Confirm saving (unless --yes)
293
+ if (!options.yes) {
294
+ console.log('');
295
+ const shouldSave = await prompts.confirm({
296
+ message: `Save spec to ${specPath}?`,
297
+ initialValue: true,
298
+ });
299
+
300
+ if (prompts.isCancel(shouldSave) || !shouldSave) {
301
+ logger.info('Spec not saved');
302
+ return;
303
+ }
304
+ }
305
+ } else {
306
+ // Use template-based generation
307
+ if (options.ai && !provider) {
308
+ logger.warn('No API key found. Falling back to template mode.');
309
+ logger.info('Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or OPENROUTER_API_KEY for AI generation.');
310
+ console.log('');
311
+ }
312
+
313
+ // Find or use default template
314
+ let templateContent: string;
315
+
316
+ // Try to find _example.md template
317
+ const exampleTemplate = await findExampleTemplate(projectRoot);
318
+ if (exampleTemplate) {
319
+ logger.info(`Using template: ${exampleTemplate}`);
320
+ templateContent = readFileSync(exampleTemplate, 'utf-8');
321
+ } else {
322
+ // Try package template
323
+ const packageTemplateDir = getPackageTemplateDir();
324
+ const packageTemplate = join(packageTemplateDir, '_example.md.tmpl');
325
+ if (existsSync(packageTemplate)) {
326
+ logger.info(`Using package template`);
327
+ templateContent = readFileSync(packageTemplate, 'utf-8');
328
+ } else {
329
+ // Use default template
330
+ logger.info('Using default template');
331
+ templateContent = DEFAULT_SPEC_TEMPLATE;
332
+ }
333
+ }
334
+
335
+ // Process template
336
+ specContent = processTemplate(templateContent, feature);
337
+
338
+ // Confirm with user (unless --yes)
339
+ if (!options.yes) {
340
+ console.log('');
341
+ console.log(pc.cyan('--- Spec Preview ---'));
342
+ console.log(`File: ${specPath}`);
343
+ console.log('');
344
+
345
+ // Show first few lines of the processed template
346
+ const previewLines = specContent.split('\n').slice(0, 15);
347
+ console.log(pc.dim(previewLines.join('\n')));
348
+ if (specContent.split('\n').length > 15) {
349
+ console.log(pc.dim('...'));
350
+ }
351
+ console.log('');
352
+
353
+ const shouldCreate = await prompts.confirm({
354
+ message: 'Create this spec file?',
355
+ initialValue: true,
356
+ });
357
+
358
+ if (prompts.isCancel(shouldCreate) || !shouldCreate) {
359
+ logger.info('Cancelled');
360
+ return;
361
+ }
362
+ }
286
363
  }
287
364
 
288
365
  // Write the spec file
@@ -308,5 +385,5 @@ export async function newCommand(feature: string, options: NewOptions = {}): Pro
308
385
  console.log('');
309
386
  console.log('Next steps:');
310
387
  console.log(` 1. Edit the spec: ${pc.cyan(`$EDITOR ${specPath}`)}`);
311
- console.log(` 2. When ready, run: ${pc.cyan(`ralph run ${feature}`)}`);
388
+ console.log(` 2. When ready, run: ${pc.cyan(`wiggum run ${feature}`)}`);
312
389
  }
@@ -94,7 +94,7 @@ export async function runCommand(feature: string, options: RunOptions = {}): Pro
94
94
 
95
95
  // Check for config
96
96
  if (!hasConfig(projectRoot)) {
97
- logger.warn('No ralph.config.js found. Run "ralph init" first to configure your project.');
97
+ logger.warn('No ralph.config.cjs found. Run "wiggum init" first to configure your project.');
98
98
  logger.info('Attempting to run with default settings...');
99
99
  console.log('');
100
100
  }
@@ -106,7 +106,7 @@ export async function runCommand(feature: string, options: RunOptions = {}): Pro
106
106
  const specFile = await validateSpecFile(projectRoot, feature);
107
107
  if (!specFile) {
108
108
  logger.error(`Spec file not found: ${feature}.md`);
109
- logger.info(`Create the spec first: ralph new ${feature}`);
109
+ logger.info(`Create the spec first: wiggum new ${feature}`);
110
110
  logger.info(`Expected location: ${join(projectRoot, config.paths.specs, `${feature}.md`)}`);
111
111
  process.exit(1);
112
112
  }
@@ -118,7 +118,7 @@ export async function runCommand(feature: string, options: RunOptions = {}): Pro
118
118
  if (!scriptPath) {
119
119
  logger.error('feature-loop.sh script not found');
120
120
  logger.info('The script should be in .ralph/scripts/ or the ralph/ directory');
121
- logger.info('Run "ralph init" to generate the necessary scripts');
121
+ logger.info('Run "wiggum init" to generate the necessary scripts');
122
122
  process.exit(1);
123
123
  }
124
124
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Config Generator
3
- * Generates ralph.config.js file from scan results
3
+ * Generates ralph.config.cjs file from scan results
4
4
  */
5
5
 
6
6
  import type { ScanResult } from '../scanner/types.js';
@@ -95,9 +95,11 @@ export function generateConfig(scanResult: ScanResult, customVars: Record<string
95
95
  }
96
96
 
97
97
  /**
98
- * Generate ralph.config.js file content as JavaScript module
98
+ * Generate ralph.config.cjs file content as CommonJS module
99
99
  */
100
100
  export function generateConfigFile(config: RalphConfig): string {
101
+ // Use CommonJS module.exports for compatibility with both CJS and ESM projects
102
+ // ESM projects can import CJS modules, but CJS projects can't use 'export default'
101
103
  const content = `module.exports = ${JSON.stringify(config, null, 2)};
102
104
  `;
103
105
 
@@ -108,7 +110,7 @@ export function generateConfigFile(config: RalphConfig): string {
108
110
  }
109
111
 
110
112
  /**
111
- * Generate ralph.config.js from scan result
113
+ * Generate ralph.config.cjs from scan result
112
114
  */
113
115
  export function generateConfigFileFromScan(scanResult: ScanResult, customVars: Record<string, string> = {}): string {
114
116
  const config = generateConfig(scanResult, customVars);
@@ -128,7 +128,7 @@ export class Generator {
128
128
  if (this.options.generateConfig) {
129
129
  config = generateConfig(scanResult, this.options.customVariables || {});
130
130
  const configContent = generateConfigFile(config);
131
- processedTemplates.set('config/ralph.config.js', configContent);
131
+ processedTemplates.set('config/ralph.config.cjs', configContent);
132
132
  }
133
133
 
134
134
  // Map template outputs to final paths
@@ -242,7 +242,7 @@ export function mapTemplateOutputPaths(templateOutputs: Map<string, string>): Ma
242
242
  // Scripts go to .ralph/scripts/
243
243
  finalPath = `.ralph/${outputPath}`;
244
244
  } else if (outputPath.startsWith('config/')) {
245
- // ralph.config.js goes to project root
245
+ // ralph.config.cjs goes to project root
246
246
  finalPath = outputPath.replace('config/', '');
247
247
  } else if (outputPath.startsWith('root/')) {
248
248
  // Root files go to .ralph/
package/src/index.ts CHANGED
@@ -1,9 +1,55 @@
1
1
  import { createCli } from './cli.js';
2
+ import { startRepl, createSessionState } from './repl/index.js';
3
+ import { hasConfig, loadConfigWithDefaults } from './utils/config.js';
4
+ import { getAvailableProvider } from './ai/providers.js';
5
+ import { displayHeader } from './utils/header.js';
6
+
7
+ /**
8
+ * Start REPL-first mode
9
+ * Called when wiggum is invoked with no arguments
10
+ */
11
+ async function startReplFirst(): Promise<void> {
12
+ const projectRoot = process.cwd();
13
+ const provider = getAvailableProvider();
14
+
15
+ // Display header
16
+ displayHeader();
17
+
18
+ // Check if already initialized
19
+ const isInitialized = hasConfig(projectRoot);
20
+ let config = null;
21
+
22
+ if (isInitialized) {
23
+ config = await loadConfigWithDefaults(projectRoot);
24
+ }
25
+
26
+ // Create initial state (may not have config yet)
27
+ const initialState = createSessionState(
28
+ projectRoot,
29
+ provider, // May be null if no API key
30
+ 'sonnet', // Default model, will be updated after /init
31
+ undefined, // No scan result yet
32
+ config,
33
+ isInitialized
34
+ );
35
+
36
+ await startRepl(initialState);
37
+ }
2
38
 
3
39
  /**
4
40
  * Main entry point for the Ralph CLI
41
+ * REPL-first: no args = start REPL, otherwise use CLI
5
42
  */
6
43
  export async function main(): Promise<void> {
44
+ const args = process.argv.slice(2);
45
+
46
+ // REPL-first: no args = start REPL
47
+ if (args.length === 0) {
48
+ await startReplFirst();
49
+ return;
50
+ }
51
+
52
+ // Legacy CLI mode for backward compatibility
7
53
  const program = createCli();
8
54
  await program.parseAsync(process.argv);
9
55
  }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Command Parser
3
+ * Parses slash commands and natural language input for the REPL
4
+ */
5
+
6
+ /**
7
+ * Parsed slash command
8
+ */
9
+ export interface SlashCommand {
10
+ /** Command name (without slash) */
11
+ name: string;
12
+ /** Command arguments */
13
+ args: string[];
14
+ /** Raw input string */
15
+ raw: string;
16
+ }
17
+
18
+ /**
19
+ * Input types
20
+ */
21
+ export type InputType = 'slash-command' | 'natural-language' | 'empty';
22
+
23
+ /**
24
+ * Parsed input result
25
+ */
26
+ export interface ParsedInput {
27
+ type: InputType;
28
+ command?: SlashCommand;
29
+ text?: string;
30
+ }
31
+
32
+ /**
33
+ * Available REPL commands
34
+ */
35
+ export const REPL_COMMANDS = {
36
+ init: {
37
+ description: 'Initialize Wiggum in this project',
38
+ usage: '/init',
39
+ aliases: ['i'],
40
+ },
41
+ new: {
42
+ description: 'Create a new feature specification',
43
+ usage: '/new <feature-name>',
44
+ aliases: ['n'],
45
+ },
46
+ run: {
47
+ description: 'Run the feature development loop',
48
+ usage: '/run <feature-name>',
49
+ aliases: ['r'],
50
+ },
51
+ monitor: {
52
+ description: 'Monitor a running feature loop',
53
+ usage: '/monitor <feature-name>',
54
+ aliases: ['m'],
55
+ },
56
+ help: {
57
+ description: 'Show available commands',
58
+ usage: '/help',
59
+ aliases: ['h', '?'],
60
+ },
61
+ exit: {
62
+ description: 'Exit the REPL',
63
+ usage: '/exit',
64
+ aliases: ['quit', 'q'],
65
+ },
66
+ } as const;
67
+
68
+ export type ReplCommandName = keyof typeof REPL_COMMANDS;
69
+
70
+ /**
71
+ * Check if input is a slash command
72
+ */
73
+ export function isSlashCommand(input: string): boolean {
74
+ return input.trim().startsWith('/');
75
+ }
76
+
77
+ /**
78
+ * Parse a slash command from input
79
+ */
80
+ export function parseSlashCommand(input: string): SlashCommand {
81
+ const trimmed = input.trim();
82
+ const parts = trimmed.slice(1).split(/\s+/);
83
+
84
+ return {
85
+ name: parts[0]?.toLowerCase() || '',
86
+ args: parts.slice(1),
87
+ raw: input,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Resolve command aliases to canonical command names
93
+ */
94
+ export function resolveCommandAlias(name: string): ReplCommandName | null {
95
+ // Check direct match
96
+ if (name in REPL_COMMANDS) {
97
+ return name as ReplCommandName;
98
+ }
99
+
100
+ // Check aliases
101
+ for (const [cmdName, cmdDef] of Object.entries(REPL_COMMANDS)) {
102
+ if ((cmdDef.aliases as readonly string[]).includes(name)) {
103
+ return cmdName as ReplCommandName;
104
+ }
105
+ }
106
+
107
+ return null;
108
+ }
109
+
110
+ /**
111
+ * Parse user input into structured format
112
+ */
113
+ export function parseInput(input: string): ParsedInput {
114
+ const trimmed = input.trim();
115
+
116
+ if (!trimmed) {
117
+ return { type: 'empty' };
118
+ }
119
+
120
+ if (isSlashCommand(trimmed)) {
121
+ return {
122
+ type: 'slash-command',
123
+ command: parseSlashCommand(trimmed),
124
+ };
125
+ }
126
+
127
+ return {
128
+ type: 'natural-language',
129
+ text: trimmed,
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Format help text for all commands
135
+ */
136
+ export function formatHelpText(): string {
137
+ const lines: string[] = [
138
+ 'Available commands:',
139
+ '',
140
+ ];
141
+
142
+ for (const [name, def] of Object.entries(REPL_COMMANDS)) {
143
+ const aliases = def.aliases.length > 0
144
+ ? ` (aliases: ${def.aliases.map(a => '/' + a).join(', ')})`
145
+ : '';
146
+ lines.push(` ${def.usage}${aliases}`);
147
+ lines.push(` ${def.description}`);
148
+ lines.push('');
149
+ }
150
+
151
+ lines.push('Or just type naturally to chat with the AI.');
152
+
153
+ return lines.join('\n');
154
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * REPL Module
3
+ * Interactive command-line interface for Wiggum
4
+ */
5
+
6
+ export { startRepl, processInput, executeCommand } from './repl-loop.js';
7
+ export {
8
+ type SessionState,
9
+ createSessionState,
10
+ updateSessionState,
11
+ } from './session-state.js';
12
+ export {
13
+ type SlashCommand,
14
+ type ParsedInput,
15
+ type InputType,
16
+ type ReplCommandName,
17
+ parseInput,
18
+ parseSlashCommand,
19
+ isSlashCommand,
20
+ resolveCommandAlias,
21
+ formatHelpText,
22
+ REPL_COMMANDS,
23
+ } from './command-parser.js';