threewzrd 1.0.6 → 1.0.8

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.
@@ -5,7 +5,7 @@ import { ToolExecutor } from '../tools/ToolExecutor.js';
5
5
  import { THREEJS_SYSTEM_PROMPT } from '../prompts/system.js';
6
6
  // Limits to prevent hitting rate limits
7
7
  const MAX_HISTORY_MESSAGES = 20; // Keep last N messages
8
- const MAX_TOKENS = 4096; // Reduced from 8192
8
+ const MAX_TOKENS = 16384; // Needs to be large enough for file contents in tool calls
9
9
  const MAX_RETRIES = 3;
10
10
  const RETRY_DELAY_MS = 5000; // 5 seconds base delay
11
11
  export class AgentEngine {
@@ -58,17 +58,14 @@ export class AgentEngine {
58
58
  }
59
59
  async runAgentLoop() {
60
60
  let continueLoop = true;
61
- let turn = 1;
62
61
  while (continueLoop) {
63
- continueLoop = await this.runSingleTurn(turn);
64
- turn++;
62
+ continueLoop = await this.runSingleTurn();
65
63
  }
66
64
  }
67
- async runSingleTurn(turn, retryCount = 0) {
65
+ async runSingleTurn(retryCount = 0) {
68
66
  try {
69
- // Show thinking indicator with turn info
70
- const thinkingMessage = turn === 1 ? 'Thinking' : 'Processing';
71
- this.ui.startThinking(thinkingMessage, turn);
67
+ // Show thinking indicator
68
+ this.ui.startThinking('Thinking');
72
69
  // Create the API request with streaming
73
70
  const stream = this.client.messages.stream({
74
71
  model: MODEL_MAP[this.model],
@@ -96,6 +93,10 @@ export class AgentEngine {
96
93
  if (!isFirstText) {
97
94
  this.ui.endStreaming();
98
95
  }
96
+ // Check for truncated response
97
+ if (response.stop_reason === 'max_tokens') {
98
+ this.ui.printWarning('Response was truncated due to length limit. Some tool calls may be incomplete.');
99
+ }
99
100
  // Process all content blocks
100
101
  for (const block of response.content) {
101
102
  contentBlocks.push(block);
@@ -111,15 +112,16 @@ export class AgentEngine {
111
112
  // No tool calls, we're done
112
113
  return false;
113
114
  }
115
+ // Show tool processing spinner
116
+ this.ui.startToolProcessing(toolUseBlocks.length);
114
117
  // Execute all tool calls
115
118
  const toolResults = [];
116
- const totalTools = toolUseBlocks.length;
117
- for (let i = 0; i < toolUseBlocks.length; i++) {
118
- const toolUse = toolUseBlocks[i];
119
- const toolProgress = totalTools > 1 ? ` (${i + 1}/${totalTools})` : '';
120
- this.ui.startThinking(`Executing ${toolUse.name}${toolProgress}`, turn);
119
+ for (const toolUse of toolUseBlocks) {
120
+ // Update spinner with current tool name
121
+ this.ui.updateToolProcessing(toolUse.name);
122
+ // Stop spinner before tool execution (which prints its own output)
123
+ this.ui.stopToolProcessing();
121
124
  const result = await this.toolExecutor.execute(toolUse.name, toolUse.input);
122
- this.ui.stopThinking();
123
125
  // Truncate large outputs to save tokens
124
126
  let output = result.success ? result.output : `Error: ${result.error}`;
125
127
  if (output.length > 2000) {
@@ -131,24 +133,30 @@ export class AgentEngine {
131
133
  content: output,
132
134
  is_error: !result.success,
133
135
  });
136
+ // Restart spinner if more tools remain
137
+ const remainingTools = toolUseBlocks.length - toolResults.length;
138
+ if (remainingTools > 0) {
139
+ this.ui.startToolProcessing(remainingTools);
140
+ }
134
141
  }
135
142
  // Add tool results to history
136
143
  this.conversationHistory.push({
137
144
  role: 'user',
138
145
  content: toolResults,
139
146
  });
140
- // Continue the loop if we have tool results to process
141
- return response.stop_reason === 'tool_use';
147
+ // Always continue after tool execution - model must see results
148
+ return true;
142
149
  }
143
150
  catch (error) {
144
151
  this.ui.stopThinking();
152
+ this.ui.stopToolProcessing();
145
153
  // Handle rate limit errors with retry
146
154
  if (error instanceof Anthropic.RateLimitError) {
147
155
  if (retryCount < MAX_RETRIES) {
148
156
  const delay = RETRY_DELAY_MS * Math.pow(2, retryCount);
149
157
  this.ui.printWarning(`Rate limited. Retrying in ${delay / 1000}s...`);
150
158
  await this.sleep(delay);
151
- return this.runSingleTurn(turn, retryCount + 1);
159
+ return this.runSingleTurn(retryCount + 1);
152
160
  }
153
161
  this.ui.printError('Rate limit exceeded. Please wait a moment and try again.');
154
162
  return false;
@@ -9,7 +9,6 @@ export declare class ThreeJsWizard {
9
9
  private workingDirectory;
10
10
  private isRunning;
11
11
  private hasOnboarded;
12
- private currentMode;
13
12
  constructor(options?: WizardOptions);
14
13
  start(): Promise<void>;
15
14
  private handleCommand;
@@ -9,7 +9,6 @@ export class ThreeJsWizard {
9
9
  workingDirectory;
10
10
  isRunning = false;
11
11
  hasOnboarded = false;
12
- currentMode = 'single-shot';
13
12
  constructor(options) {
14
13
  this.workingDirectory = process.cwd();
15
14
  this.ui = new TerminalUI();
@@ -43,7 +42,6 @@ export class ThreeJsWizard {
43
42
  if (!this.hasOnboarded && isEmptyDir) {
44
43
  const preferences = await runOnboarding(this.ui);
45
44
  this.hasOnboarded = true;
46
- this.currentMode = preferences.mode;
47
45
  // Process the initial project request
48
46
  const contextMessage = buildContextMessage(preferences);
49
47
  await this.engine.processMessage(contextMessage);
@@ -55,8 +53,7 @@ export class ThreeJsWizard {
55
53
  // Main REPL loop
56
54
  while (this.isRunning) {
57
55
  try {
58
- const { text: input, mode } = await this.ui.promptWithMode(this.currentMode);
59
- this.currentMode = mode;
56
+ const input = await this.ui.prompt();
60
57
  if (!input) {
61
58
  continue;
62
59
  }
@@ -65,13 +62,8 @@ export class ThreeJsWizard {
65
62
  await this.handleCommand(input);
66
63
  continue;
67
64
  }
68
- // Build message with mode context
69
- const modePrefix = mode === 'planning'
70
- ? '[PLANNING MODE] Output a detailed implementation plan before coding.\n\n'
71
- : '';
72
- const fullMessage = modePrefix + input;
73
65
  // Process user message through agent
74
- await this.engine.processMessage(fullMessage);
66
+ await this.engine.processMessage(input);
75
67
  // Track created files
76
68
  for (const file of this.engine.getCreatedFiles()) {
77
69
  this.projectManager.addFile(file);
@@ -4,12 +4,10 @@ export declare const MODEL_MAP: Record<ModelId, string>;
4
4
  export declare const DEFAULT_MODEL: ModelId;
5
5
  export type ProjectLanguage = 'javascript' | 'typescript';
6
6
  export type ProjectTarget = 'browser' | 'mobile' | 'desktop';
7
- export type ExecutionMode = 'single-shot' | 'planning';
8
7
  export interface ProjectPreferences {
9
8
  language: ProjectLanguage;
10
9
  target: ProjectTarget;
11
10
  description: string;
12
- mode: ExecutionMode;
13
11
  }
14
12
  export interface ProjectConfig {
15
13
  name: string;
@@ -1,7 +1,6 @@
1
1
  import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import { spawn } from 'child_process';
4
- import chalk from 'chalk';
5
4
  import { shouldValidate, validate } from './CodeValidator.js';
6
5
  // Whitelist of allowed commands for security
7
6
  const ALLOWED_COMMANDS = new Set([
@@ -59,20 +58,21 @@ export class ToolExecutor {
59
58
  */
60
59
  validateWriteFileInput(input) {
61
60
  if (!input || typeof input !== 'object') {
62
- throw new Error('Invalid input: expected object');
61
+ throw new Error(`Invalid input: expected object, got ${typeof input}: ${JSON.stringify(input)}`);
63
62
  }
64
63
  const obj = input;
65
64
  if (typeof obj.path !== 'string' || !obj.path.trim()) {
66
- throw new Error('Invalid input: path must be a non-empty string');
65
+ throw new Error(`Invalid input: path must be a non-empty string. Received keys: ${Object.keys(obj).join(', ')}`);
66
+ }
67
+ // Content is required - reject null/undefined
68
+ if (obj.content === null || obj.content === undefined) {
69
+ throw new Error(`Invalid input: content is required. Received keys: [${Object.keys(obj).join(', ')}], content type: ${typeof obj.content}`);
67
70
  }
68
71
  // Coerce content to string - handle various types the model might return
69
72
  let content;
70
73
  if (typeof obj.content === 'string') {
71
74
  content = obj.content;
72
75
  }
73
- else if (obj.content === null || obj.content === undefined) {
74
- content = '';
75
- }
76
76
  else if (Array.isArray(obj.content)) {
77
77
  // Model sometimes returns content as an array of strings
78
78
  content = obj.content.map(item => String(item)).join('\n');
@@ -85,6 +85,10 @@ export class ToolExecutor {
85
85
  // Fallback for numbers, booleans, etc.
86
86
  content = String(obj.content);
87
87
  }
88
+ // Reject empty content - likely a model error
89
+ if (!content.trim()) {
90
+ throw new Error('Invalid input: content cannot be empty');
91
+ }
88
92
  return {
89
93
  path: obj.path.trim(),
90
94
  content,
@@ -233,7 +237,6 @@ export class ToolExecutor {
233
237
  try {
234
238
  // Validate input structure
235
239
  const validatedInput = this.validateWriteFileInput(input);
236
- this.ui.startToolSpinner('write_file', `Writing ${validatedInput.path}`);
237
240
  // Validate path doesn't escape working directory
238
241
  const fullPath = this.validatePath(validatedInput.path);
239
242
  const dir = path.dirname(fullPath);
@@ -243,7 +246,8 @@ export class ToolExecutor {
243
246
  // If there are errors, don't write the file
244
247
  if (!validationResult.valid) {
245
248
  const errorDetails = validationResult.errors.join('\n - ');
246
- this.ui.failToolSpinner('Syntax validation failed');
249
+ this.ui.printToolCall('write_file', `Writing: ${validatedInput.path}`);
250
+ this.ui.printToolResult(false, 'Syntax validation failed');
247
251
  return {
248
252
  success: false,
249
253
  output: '',
@@ -262,7 +266,8 @@ export class ToolExecutor {
262
266
  // Write the file
263
267
  await fs.writeFile(fullPath, validatedInput.content, 'utf-8');
264
268
  this.createdFiles.add(validatedInput.path);
265
- this.ui.succeedToolSpinner(`Wrote ${validatedInput.path}`);
269
+ this.ui.printToolCall('write_file', `Writing: ${validatedInput.path}`);
270
+ this.ui.printToolResult(true, '');
266
271
  return {
267
272
  success: true,
268
273
  output: `Successfully wrote ${validatedInput.path}`,
@@ -270,7 +275,9 @@ export class ToolExecutor {
270
275
  }
271
276
  catch (error) {
272
277
  const errorMessage = error instanceof Error ? error.message : String(error);
273
- this.ui.failToolSpinner(`Failed to write: ${errorMessage}`);
278
+ const displayPath = input?.path || 'unknown';
279
+ this.ui.printToolCall('write_file', `Writing: ${displayPath}`);
280
+ this.ui.printToolResult(false, errorMessage);
274
281
  return {
275
282
  success: false,
276
283
  output: '',
@@ -282,11 +289,11 @@ export class ToolExecutor {
282
289
  try {
283
290
  // Validate input structure
284
291
  const validatedInput = this.validateReadFileInput(input);
285
- this.ui.startToolSpinner('read_file', `Reading ${validatedInput.path}`);
286
292
  // Validate path doesn't escape working directory
287
293
  const fullPath = this.validatePath(validatedInput.path);
288
294
  const content = await fs.readFile(fullPath, 'utf-8');
289
- this.ui.succeedToolSpinner(`Read ${validatedInput.path}`);
295
+ this.ui.printToolCall('read_file', `Reading: ${validatedInput.path}`);
296
+ this.ui.printToolResult(true, '');
290
297
  return {
291
298
  success: true,
292
299
  output: content,
@@ -294,7 +301,9 @@ export class ToolExecutor {
294
301
  }
295
302
  catch (error) {
296
303
  const errorMessage = error instanceof Error ? error.message : String(error);
297
- this.ui.failToolSpinner(`Failed to read: ${errorMessage}`);
304
+ const displayPath = input?.path || 'unknown';
305
+ this.ui.printToolCall('read_file', `Reading: ${displayPath}`);
306
+ this.ui.printToolResult(false, errorMessage);
298
307
  return {
299
308
  success: false,
300
309
  output: '',
@@ -314,20 +323,17 @@ export class ToolExecutor {
314
323
  if (validatedInput.cwd) {
315
324
  cwd = this.validatePath(validatedInput.cwd);
316
325
  }
317
- // Show command for user confirmation
318
- console.log();
319
- console.log(chalk.yellow(`[Command] `) + chalk.gray(validatedInput.command));
326
+ this.ui.printToolCall('run_command', `Command: ${validatedInput.command}`);
320
327
  // Ask for user confirmation before running any command
321
328
  const approved = await this.ui.confirm(`Run this command?`);
322
329
  if (!approved) {
323
- console.log(chalk.red(' Declined'));
330
+ this.ui.printToolResult(false, 'User declined');
324
331
  return {
325
332
  success: false,
326
333
  output: '',
327
334
  error: 'User declined to run this command',
328
335
  };
329
336
  }
330
- this.ui.startToolSpinner('run_command', `Running: ${validatedInput.command}`);
331
337
  // For piped commands, use shell with pre-validated command string
332
338
  // For non-piped commands, use spawn without shell for security
333
339
  return new Promise((resolve) => {
@@ -354,14 +360,14 @@ export class ToolExecutor {
354
360
  child.on('close', (code) => {
355
361
  const output = stdout + (stderr ? `\nStderr: ${stderr}` : '');
356
362
  if (code === 0) {
357
- this.ui.succeedToolSpinner('Command completed');
363
+ this.ui.printToolResult(true, '');
358
364
  resolve({
359
365
  success: true,
360
366
  output: output || 'Command completed successfully',
361
367
  });
362
368
  }
363
369
  else {
364
- this.ui.failToolSpinner(`Exit code: ${code}`);
370
+ this.ui.printToolResult(false, `Exit code: ${code}`);
365
371
  resolve({
366
372
  success: false,
367
373
  output: '',
@@ -370,7 +376,7 @@ export class ToolExecutor {
370
376
  }
371
377
  });
372
378
  child.on('error', (error) => {
373
- this.ui.failToolSpinner(error.message);
379
+ this.ui.printToolResult(false, error.message);
374
380
  resolve({
375
381
  success: false,
376
382
  output: '',
@@ -381,7 +387,9 @@ export class ToolExecutor {
381
387
  }
382
388
  catch (error) {
383
389
  const errorMessage = error instanceof Error ? error.message : String(error);
384
- this.ui.failToolSpinner(`Failed: ${errorMessage}`);
390
+ const displayCmd = input?.command || 'unknown';
391
+ this.ui.printToolCall('run_command', `Command: ${displayCmd}`);
392
+ this.ui.printToolResult(false, errorMessage);
385
393
  return {
386
394
  success: false,
387
395
  output: '',
@@ -393,16 +401,15 @@ export class ToolExecutor {
393
401
  try {
394
402
  // Validate input structure
395
403
  const validatedInput = this.validateListFilesInput(input);
396
- const displayPath = validatedInput.path || '.';
397
- this.ui.startToolSpinner('list_files', `Listing ${displayPath}`);
398
404
  // Validate path doesn't escape working directory
399
405
  const targetPath = validatedInput.path
400
406
  ? this.validatePath(validatedInput.path)
401
407
  : this.workingDirectory;
408
+ this.ui.printToolCall('list_files', `Listing: ${validatedInput.path || '.'}`);
402
409
  const files = await this.listFilesRecursive(targetPath, validatedInput.recursive ?? false);
403
410
  // Format output
404
411
  const relativePaths = files.map(f => path.relative(this.workingDirectory, f));
405
- this.ui.succeedToolSpinner(`Listed ${relativePaths.length} items`);
412
+ this.ui.printToolResult(true, '');
406
413
  return {
407
414
  success: true,
408
415
  output: relativePaths.length > 0
@@ -412,7 +419,9 @@ export class ToolExecutor {
412
419
  }
413
420
  catch (error) {
414
421
  const errorMessage = error instanceof Error ? error.message : String(error);
415
- this.ui.failToolSpinner(`Failed to list: ${errorMessage}`);
422
+ const displayPath = input?.path || '.';
423
+ this.ui.printToolCall('list_files', `Listing: ${displayPath}`);
424
+ this.ui.printToolResult(false, errorMessage);
416
425
  return {
417
426
  success: false,
418
427
  output: '',
@@ -1,4 +1,4 @@
1
- import { ModelId, ExecutionMode } from '../core/types.js';
1
+ import { ModelId } from '../core/types.js';
2
2
  export interface SelectOption {
3
3
  label: string;
4
4
  value: string;
@@ -9,13 +9,11 @@ export declare class TerminalUI {
9
9
  private thinkingSpinner;
10
10
  private toolSpinner;
11
11
  constructor();
12
- startThinking(message?: string, turn?: number): void;
13
- updateThinking(message: string, turn?: number): void;
12
+ startThinking(message?: string): void;
14
13
  stopThinking(): void;
15
- startToolSpinner(toolName: string, detail: string): void;
16
- succeedToolSpinner(message?: string): void;
17
- failToolSpinner(message: string): void;
18
- stopToolSpinner(): void;
14
+ startToolProcessing(toolCount: number): void;
15
+ updateToolProcessing(toolName: string): void;
16
+ stopToolProcessing(): void;
19
17
  confirm(message: string): Promise<boolean>;
20
18
  select(question: string, options: SelectOption[]): Promise<string>;
21
19
  printBanner(): void;
@@ -37,11 +35,6 @@ export declare class TerminalUI {
37
35
  streamText(text: string): void;
38
36
  endStreaming(): void;
39
37
  prompt(): Promise<string>;
40
- promptWithMode(defaultMode?: ExecutionMode): Promise<{
41
- text: string;
42
- mode: ExecutionMode;
43
- }>;
44
- printModeInfo(mode: ExecutionMode): void;
45
38
  close(): void;
46
39
  clearScreen(): void;
47
40
  }
@@ -12,21 +12,14 @@ export class TerminalUI {
12
12
  output: process.stdout,
13
13
  });
14
14
  }
15
- // Thinking indicator with turn info
16
- startThinking(message = 'Thinking', turn) {
17
- const turnInfo = turn ? chalk.gray(` [Turn ${turn}]`) : '';
15
+ // Thinking indicator
16
+ startThinking(message = 'Thinking') {
18
17
  this.thinkingSpinner = ora({
19
- text: chalk.cyan(message) + turnInfo,
18
+ text: chalk.cyan(message),
20
19
  spinner: 'dots',
21
20
  discardStdin: false, // Don't interfere with readline
22
21
  }).start();
23
22
  }
24
- updateThinking(message, turn) {
25
- if (this.thinkingSpinner) {
26
- const turnInfo = turn ? chalk.gray(` [Turn ${turn}]`) : '';
27
- this.thinkingSpinner.text = chalk.cyan(message) + turnInfo;
28
- }
29
- }
30
23
  stopThinking() {
31
24
  if (this.thinkingSpinner) {
32
25
  this.thinkingSpinner.stop();
@@ -37,27 +30,21 @@ export class TerminalUI {
37
30
  process.stdin.resume();
38
31
  }
39
32
  }
40
- // Tool execution spinner
41
- startToolSpinner(toolName, detail) {
33
+ // Tool processing spinner
34
+ startToolProcessing(toolCount) {
35
+ const plural = toolCount > 1 ? 's' : '';
42
36
  this.toolSpinner = ora({
43
- text: chalk.yellow(`${toolName}: `) + chalk.gray(detail),
37
+ text: chalk.yellow(`Executing ${toolCount} tool${plural}...`),
44
38
  spinner: 'dots',
45
39
  discardStdin: false,
46
40
  }).start();
47
41
  }
48
- succeedToolSpinner(message) {
49
- if (this.toolSpinner) {
50
- this.toolSpinner.succeed(message ? chalk.green(message) : undefined);
51
- this.toolSpinner = null;
52
- }
53
- }
54
- failToolSpinner(message) {
42
+ updateToolProcessing(toolName) {
55
43
  if (this.toolSpinner) {
56
- this.toolSpinner.fail(chalk.red(message));
57
- this.toolSpinner = null;
44
+ this.toolSpinner.text = chalk.yellow(`Executing: ${toolName}...`);
58
45
  }
59
46
  }
60
- stopToolSpinner() {
47
+ stopToolProcessing() {
61
48
  if (this.toolSpinner) {
62
49
  this.toolSpinner.stop();
63
50
  this.toolSpinner = null;
@@ -112,18 +99,12 @@ export class TerminalUI {
112
99
  console.log(chalk.gray(' lighting, animations, and more - just describe what'));
113
100
  console.log(chalk.gray(' you want in plain English.'));
114
101
  console.log();
115
- console.log(chalk.gray(' Modes: ') + chalk.cyan('⚡ Quick') + chalk.gray(' | ') + chalk.magenta('📋 Plan') + chalk.gray(' (Tab to switch)'));
116
102
  console.log(chalk.gray(' Commands: ') + chalk.yellow('/help') + chalk.gray(' | ') + chalk.yellow('/clear') + chalk.gray(' | ') + chalk.yellow('/exit'));
117
103
  console.log();
118
104
  console.log(chalk.gray(' ─────────────────────────────────────────'));
119
105
  console.log();
120
106
  }
121
107
  printHelp() {
122
- console.log();
123
- console.log(chalk.yellow('Execution Modes:'));
124
- console.log(chalk.cyan(' ⚡ Quick Mode') + chalk.gray(' - Direct implementation (default)'));
125
- console.log(chalk.magenta(' 📋 Plan Mode') + chalk.gray(' - Detailed architecture plan first'));
126
- console.log(chalk.gray(' Press Tab while typing to switch modes'));
127
108
  console.log();
128
109
  console.log(chalk.yellow('Commands:'));
129
110
  console.log(chalk.cyan(' /help') + chalk.gray(' - Show this help message'));
@@ -216,100 +197,6 @@ export class TerminalUI {
216
197
  });
217
198
  });
218
199
  }
219
- // Mode-aware prompt with Tab toggle for execution mode
220
- async promptWithMode(defaultMode = 'single-shot') {
221
- let currentMode = defaultMode;
222
- // Print mode indicator and instructions
223
- const printModeBar = () => {
224
- const singleShot = currentMode === 'single-shot'
225
- ? chalk.bgCyan.black(' ⚡ Quick ')
226
- : chalk.gray(' ⚡ Quick ');
227
- const planning = currentMode === 'planning'
228
- ? chalk.bgMagenta.white(' 📋 Plan ')
229
- : chalk.gray(' 📋 Plan ');
230
- // Clear line and reprint
231
- process.stdout.write('\r\x1b[K');
232
- process.stdout.write(` ${singleShot} ${planning} ${chalk.gray('Tab to switch')}\n`);
233
- };
234
- return new Promise((resolve) => {
235
- printModeBar();
236
- console.log();
237
- // Close the existing readline interface to fully release stdin
238
- this.rl.close();
239
- // Store the current input
240
- let inputBuffer = '';
241
- // Set up raw mode for keypress detection
242
- if (process.stdin.isTTY) {
243
- process.stdin.setRawMode(true);
244
- }
245
- process.stdin.resume();
246
- const promptPrefix = chalk.magenta(' › ');
247
- process.stdout.write(promptPrefix);
248
- const handleKeypress = (chunk) => {
249
- const key = chunk.toString();
250
- // Tab key - toggle mode
251
- if (key === '\t') {
252
- currentMode = currentMode === 'single-shot' ? 'planning' : 'single-shot';
253
- // Move cursor up, clear the mode bar, reprint it, move back down
254
- process.stdout.write('\x1b[2A'); // Move up 2 lines
255
- printModeBar();
256
- console.log();
257
- process.stdout.write(promptPrefix + inputBuffer);
258
- return;
259
- }
260
- // Enter key - submit
261
- if (key === '\r' || key === '\n') {
262
- process.stdout.write('\n');
263
- cleanup();
264
- resolve({ text: inputBuffer.trim(), mode: currentMode });
265
- return;
266
- }
267
- // Ctrl+C - exit
268
- if (key === '\x03') {
269
- cleanup();
270
- process.exit(0);
271
- }
272
- // Backspace
273
- if (key === '\x7f' || key === '\b') {
274
- if (inputBuffer.length > 0) {
275
- inputBuffer = inputBuffer.slice(0, -1);
276
- process.stdout.write('\b \b');
277
- }
278
- return;
279
- }
280
- // Escape sequences (arrow keys, etc.) - ignore for simplicity
281
- if (key.startsWith('\x1b')) {
282
- return;
283
- }
284
- // Regular character
285
- if (key.length === 1 && key.charCodeAt(0) >= 32) {
286
- inputBuffer += key;
287
- process.stdout.write(key);
288
- }
289
- };
290
- const cleanup = () => {
291
- process.stdin.removeListener('data', handleKeypress);
292
- if (process.stdin.isTTY) {
293
- process.stdin.setRawMode(false);
294
- }
295
- // Recreate readline interface
296
- this.rl = readline.createInterface({
297
- input: process.stdin,
298
- output: process.stdout,
299
- });
300
- };
301
- process.stdin.on('data', handleKeypress);
302
- });
303
- }
304
- printModeInfo(mode) {
305
- if (mode === 'single-shot') {
306
- console.log(chalk.cyan(' ⚡ Quick Mode: ') + chalk.gray('Direct implementation without formal planning'));
307
- }
308
- else {
309
- console.log(chalk.magenta(' 📋 Plan Mode: ') + chalk.gray('Detailed architecture plan before implementation'));
310
- }
311
- console.log();
312
- }
313
200
  close() {
314
201
  this.rl.close();
315
202
  }
@@ -12,33 +12,27 @@ export async function runOnboarding(ui) {
12
12
  { label: 'Mobile (React Native, etc.)', value: 'mobile' },
13
13
  { label: 'Desktop (Electron, etc.)', value: 'desktop' },
14
14
  ]);
15
- // Ask for project description with mode toggle
15
+ // Ask for project description
16
16
  console.log();
17
- console.log(chalk.cyan(' Describe what you\'d like to build:'));
17
+ console.log(chalk.cyan(' Now describe what you\'d like to build:'));
18
18
  console.log(chalk.gray(' (e.g., "A 3D solar system with orbiting planets")'));
19
19
  console.log();
20
- const { text: description, mode } = await ui.promptWithMode('single-shot');
20
+ const description = await ui.prompt();
21
21
  console.log();
22
- ui.printModeInfo(mode);
23
22
  console.log(chalk.gray(' ─────────────────────────────────────────'));
24
23
  console.log();
25
24
  return {
26
25
  language,
27
26
  target,
28
27
  description,
29
- mode,
30
28
  };
31
29
  }
32
30
  export function buildContextMessage(prefs) {
33
- const modeInstruction = prefs.mode === 'planning'
34
- ? `\n\nIMPORTANT: The user has requested Planning Mode. You MUST output a detailed implementation plan with Architecture Overview, Dependencies, File Structure, and Execution Steps BEFORE writing any code.`
35
- : `\n\nThe user has requested Single-Shot Mode. Plan internally and proceed directly to implementation without outputting a formal plan.`;
36
31
  return `The user wants to create a Three.js project with these preferences:
37
32
  - Language: ${prefs.language}
38
33
  - Target platform: ${prefs.target}
39
- - Execution mode: ${prefs.mode === 'planning' ? 'Planning Mode (detailed plan first)' : 'Single-Shot Mode (direct implementation)'}
40
34
 
41
- Their project description: ${prefs.description}${modeInstruction}
35
+ Their project description: ${prefs.description}
42
36
 
43
37
  Please create the project structure and initial files based on these requirements. Start by setting up the basic Three.js scene.`;
44
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threewzrd",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "AI-powered CLI for generating Three.js projects from natural language",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {