threewzrd 1.0.4 → 1.0.6

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.
@@ -58,14 +58,17 @@ export class AgentEngine {
58
58
  }
59
59
  async runAgentLoop() {
60
60
  let continueLoop = true;
61
+ let turn = 1;
61
62
  while (continueLoop) {
62
- continueLoop = await this.runSingleTurn();
63
+ continueLoop = await this.runSingleTurn(turn);
64
+ turn++;
63
65
  }
64
66
  }
65
- async runSingleTurn(retryCount = 0) {
67
+ async runSingleTurn(turn, retryCount = 0) {
66
68
  try {
67
- // Show thinking indicator
68
- this.ui.startThinking('Thinking');
69
+ // Show thinking indicator with turn info
70
+ const thinkingMessage = turn === 1 ? 'Thinking' : 'Processing';
71
+ this.ui.startThinking(thinkingMessage, turn);
69
72
  // Create the API request with streaming
70
73
  const stream = this.client.messages.stream({
71
74
  model: MODEL_MAP[this.model],
@@ -110,8 +113,13 @@ export class AgentEngine {
110
113
  }
111
114
  // Execute all tool calls
112
115
  const toolResults = [];
113
- for (const toolUse of toolUseBlocks) {
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);
114
121
  const result = await this.toolExecutor.execute(toolUse.name, toolUse.input);
122
+ this.ui.stopThinking();
115
123
  // Truncate large outputs to save tokens
116
124
  let output = result.success ? result.output : `Error: ${result.error}`;
117
125
  if (output.length > 2000) {
@@ -140,7 +148,7 @@ export class AgentEngine {
140
148
  const delay = RETRY_DELAY_MS * Math.pow(2, retryCount);
141
149
  this.ui.printWarning(`Rate limited. Retrying in ${delay / 1000}s...`);
142
150
  await this.sleep(delay);
143
- return this.runSingleTurn(retryCount + 1);
151
+ return this.runSingleTurn(turn, retryCount + 1);
144
152
  }
145
153
  this.ui.printError('Rate limit exceeded. Please wait a moment and try again.');
146
154
  return false;
@@ -9,6 +9,7 @@ export declare class ThreeJsWizard {
9
9
  private workingDirectory;
10
10
  private isRunning;
11
11
  private hasOnboarded;
12
+ private currentMode;
12
13
  constructor(options?: WizardOptions);
13
14
  start(): Promise<void>;
14
15
  private handleCommand;
@@ -9,6 +9,7 @@ export class ThreeJsWizard {
9
9
  workingDirectory;
10
10
  isRunning = false;
11
11
  hasOnboarded = false;
12
+ currentMode = 'single-shot';
12
13
  constructor(options) {
13
14
  this.workingDirectory = process.cwd();
14
15
  this.ui = new TerminalUI();
@@ -42,6 +43,7 @@ export class ThreeJsWizard {
42
43
  if (!this.hasOnboarded && isEmptyDir) {
43
44
  const preferences = await runOnboarding(this.ui);
44
45
  this.hasOnboarded = true;
46
+ this.currentMode = preferences.mode;
45
47
  // Process the initial project request
46
48
  const contextMessage = buildContextMessage(preferences);
47
49
  await this.engine.processMessage(contextMessage);
@@ -53,7 +55,8 @@ export class ThreeJsWizard {
53
55
  // Main REPL loop
54
56
  while (this.isRunning) {
55
57
  try {
56
- const input = await this.ui.prompt();
58
+ const { text: input, mode } = await this.ui.promptWithMode(this.currentMode);
59
+ this.currentMode = mode;
57
60
  if (!input) {
58
61
  continue;
59
62
  }
@@ -62,8 +65,13 @@ export class ThreeJsWizard {
62
65
  await this.handleCommand(input);
63
66
  continue;
64
67
  }
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;
65
73
  // Process user message through agent
66
- await this.engine.processMessage(input);
74
+ await this.engine.processMessage(fullMessage);
67
75
  // Track created files
68
76
  for (const file of this.engine.getCreatedFiles()) {
69
77
  this.projectManager.addFile(file);
@@ -4,10 +4,12 @@ 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';
7
8
  export interface ProjectPreferences {
8
9
  language: ProjectLanguage;
9
10
  target: ProjectTarget;
10
11
  description: string;
12
+ mode: ExecutionMode;
11
13
  }
12
14
  export interface ProjectConfig {
13
15
  name: string;
@@ -1,6 +1,7 @@
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';
4
5
  import { shouldValidate, validate } from './CodeValidator.js';
5
6
  // Whitelist of allowed commands for security
6
7
  const ALLOWED_COMMANDS = new Set([
@@ -64,12 +65,29 @@ export class ToolExecutor {
64
65
  if (typeof obj.path !== 'string' || !obj.path.trim()) {
65
66
  throw new Error('Invalid input: path must be a non-empty string');
66
67
  }
67
- if (typeof obj.content !== 'string') {
68
- throw new Error('Invalid input: content must be a string');
68
+ // Coerce content to string - handle various types the model might return
69
+ let content;
70
+ if (typeof obj.content === 'string') {
71
+ content = obj.content;
72
+ }
73
+ else if (obj.content === null || obj.content === undefined) {
74
+ content = '';
75
+ }
76
+ else if (Array.isArray(obj.content)) {
77
+ // Model sometimes returns content as an array of strings
78
+ content = obj.content.map(item => String(item)).join('\n');
79
+ }
80
+ else if (typeof obj.content === 'object') {
81
+ // Fallback: stringify objects
82
+ content = JSON.stringify(obj.content, null, 2);
83
+ }
84
+ else {
85
+ // Fallback for numbers, booleans, etc.
86
+ content = String(obj.content);
69
87
  }
70
88
  return {
71
89
  path: obj.path.trim(),
72
- content: obj.content,
90
+ content,
73
91
  skipValidation: obj.skipValidation === true
74
92
  };
75
93
  }
@@ -215,6 +233,7 @@ export class ToolExecutor {
215
233
  try {
216
234
  // Validate input structure
217
235
  const validatedInput = this.validateWriteFileInput(input);
236
+ this.ui.startToolSpinner('write_file', `Writing ${validatedInput.path}`);
218
237
  // Validate path doesn't escape working directory
219
238
  const fullPath = this.validatePath(validatedInput.path);
220
239
  const dir = path.dirname(fullPath);
@@ -224,8 +243,7 @@ export class ToolExecutor {
224
243
  // If there are errors, don't write the file
225
244
  if (!validationResult.valid) {
226
245
  const errorDetails = validationResult.errors.join('\n - ');
227
- this.ui.printToolCall('write_file', `Writing: ${validatedInput.path}`);
228
- this.ui.printToolResult(false, 'Syntax validation failed');
246
+ this.ui.failToolSpinner('Syntax validation failed');
229
247
  return {
230
248
  success: false,
231
249
  output: '',
@@ -244,8 +262,7 @@ export class ToolExecutor {
244
262
  // Write the file
245
263
  await fs.writeFile(fullPath, validatedInput.content, 'utf-8');
246
264
  this.createdFiles.add(validatedInput.path);
247
- this.ui.printToolCall('write_file', `Writing: ${validatedInput.path}`);
248
- this.ui.printToolResult(true, '');
265
+ this.ui.succeedToolSpinner(`Wrote ${validatedInput.path}`);
249
266
  return {
250
267
  success: true,
251
268
  output: `Successfully wrote ${validatedInput.path}`,
@@ -253,9 +270,7 @@ export class ToolExecutor {
253
270
  }
254
271
  catch (error) {
255
272
  const errorMessage = error instanceof Error ? error.message : String(error);
256
- const displayPath = input?.path || 'unknown';
257
- this.ui.printToolCall('write_file', `Writing: ${displayPath}`);
258
- this.ui.printToolResult(false, errorMessage);
273
+ this.ui.failToolSpinner(`Failed to write: ${errorMessage}`);
259
274
  return {
260
275
  success: false,
261
276
  output: '',
@@ -267,11 +282,11 @@ export class ToolExecutor {
267
282
  try {
268
283
  // Validate input structure
269
284
  const validatedInput = this.validateReadFileInput(input);
285
+ this.ui.startToolSpinner('read_file', `Reading ${validatedInput.path}`);
270
286
  // Validate path doesn't escape working directory
271
287
  const fullPath = this.validatePath(validatedInput.path);
272
288
  const content = await fs.readFile(fullPath, 'utf-8');
273
- this.ui.printToolCall('read_file', `Reading: ${validatedInput.path}`);
274
- this.ui.printToolResult(true, '');
289
+ this.ui.succeedToolSpinner(`Read ${validatedInput.path}`);
275
290
  return {
276
291
  success: true,
277
292
  output: content,
@@ -279,9 +294,7 @@ export class ToolExecutor {
279
294
  }
280
295
  catch (error) {
281
296
  const errorMessage = error instanceof Error ? error.message : String(error);
282
- const displayPath = input?.path || 'unknown';
283
- this.ui.printToolCall('read_file', `Reading: ${displayPath}`);
284
- this.ui.printToolResult(false, errorMessage);
297
+ this.ui.failToolSpinner(`Failed to read: ${errorMessage}`);
285
298
  return {
286
299
  success: false,
287
300
  output: '',
@@ -301,17 +314,20 @@ export class ToolExecutor {
301
314
  if (validatedInput.cwd) {
302
315
  cwd = this.validatePath(validatedInput.cwd);
303
316
  }
304
- this.ui.printToolCall('run_command', `Command: ${validatedInput.command}`);
317
+ // Show command for user confirmation
318
+ console.log();
319
+ console.log(chalk.yellow(`[Command] `) + chalk.gray(validatedInput.command));
305
320
  // Ask for user confirmation before running any command
306
321
  const approved = await this.ui.confirm(`Run this command?`);
307
322
  if (!approved) {
308
- this.ui.printToolResult(false, 'User declined');
323
+ console.log(chalk.red(' Declined'));
309
324
  return {
310
325
  success: false,
311
326
  output: '',
312
327
  error: 'User declined to run this command',
313
328
  };
314
329
  }
330
+ this.ui.startToolSpinner('run_command', `Running: ${validatedInput.command}`);
315
331
  // For piped commands, use shell with pre-validated command string
316
332
  // For non-piped commands, use spawn without shell for security
317
333
  return new Promise((resolve) => {
@@ -338,14 +354,14 @@ export class ToolExecutor {
338
354
  child.on('close', (code) => {
339
355
  const output = stdout + (stderr ? `\nStderr: ${stderr}` : '');
340
356
  if (code === 0) {
341
- this.ui.printToolResult(true, '');
357
+ this.ui.succeedToolSpinner('Command completed');
342
358
  resolve({
343
359
  success: true,
344
360
  output: output || 'Command completed successfully',
345
361
  });
346
362
  }
347
363
  else {
348
- this.ui.printToolResult(false, `Exit code: ${code}`);
364
+ this.ui.failToolSpinner(`Exit code: ${code}`);
349
365
  resolve({
350
366
  success: false,
351
367
  output: '',
@@ -354,7 +370,7 @@ export class ToolExecutor {
354
370
  }
355
371
  });
356
372
  child.on('error', (error) => {
357
- this.ui.printToolResult(false, error.message);
373
+ this.ui.failToolSpinner(error.message);
358
374
  resolve({
359
375
  success: false,
360
376
  output: '',
@@ -365,9 +381,7 @@ export class ToolExecutor {
365
381
  }
366
382
  catch (error) {
367
383
  const errorMessage = error instanceof Error ? error.message : String(error);
368
- const displayCmd = input?.command || 'unknown';
369
- this.ui.printToolCall('run_command', `Command: ${displayCmd}`);
370
- this.ui.printToolResult(false, errorMessage);
384
+ this.ui.failToolSpinner(`Failed: ${errorMessage}`);
371
385
  return {
372
386
  success: false,
373
387
  output: '',
@@ -379,15 +393,16 @@ export class ToolExecutor {
379
393
  try {
380
394
  // Validate input structure
381
395
  const validatedInput = this.validateListFilesInput(input);
396
+ const displayPath = validatedInput.path || '.';
397
+ this.ui.startToolSpinner('list_files', `Listing ${displayPath}`);
382
398
  // Validate path doesn't escape working directory
383
399
  const targetPath = validatedInput.path
384
400
  ? this.validatePath(validatedInput.path)
385
401
  : this.workingDirectory;
386
- this.ui.printToolCall('list_files', `Listing: ${validatedInput.path || '.'}`);
387
402
  const files = await this.listFilesRecursive(targetPath, validatedInput.recursive ?? false);
388
403
  // Format output
389
404
  const relativePaths = files.map(f => path.relative(this.workingDirectory, f));
390
- this.ui.printToolResult(true, '');
405
+ this.ui.succeedToolSpinner(`Listed ${relativePaths.length} items`);
391
406
  return {
392
407
  success: true,
393
408
  output: relativePaths.length > 0
@@ -397,9 +412,7 @@ export class ToolExecutor {
397
412
  }
398
413
  catch (error) {
399
414
  const errorMessage = error instanceof Error ? error.message : String(error);
400
- const displayPath = input?.path || '.';
401
- this.ui.printToolCall('list_files', `Listing: ${displayPath}`);
402
- this.ui.printToolResult(false, errorMessage);
415
+ this.ui.failToolSpinner(`Failed to list: ${errorMessage}`);
403
416
  return {
404
417
  success: false,
405
418
  output: '',
@@ -1,4 +1,4 @@
1
- import { ModelId } from '../core/types.js';
1
+ import { ModelId, ExecutionMode } from '../core/types.js';
2
2
  export interface SelectOption {
3
3
  label: string;
4
4
  value: string;
@@ -7,9 +7,15 @@ export declare class TerminalUI {
7
7
  private rl;
8
8
  private isStreaming;
9
9
  private thinkingSpinner;
10
+ private toolSpinner;
10
11
  constructor();
11
- startThinking(message?: string): void;
12
+ startThinking(message?: string, turn?: number): void;
13
+ updateThinking(message: string, turn?: number): void;
12
14
  stopThinking(): void;
15
+ startToolSpinner(toolName: string, detail: string): void;
16
+ succeedToolSpinner(message?: string): void;
17
+ failToolSpinner(message: string): void;
18
+ stopToolSpinner(): void;
13
19
  confirm(message: string): Promise<boolean>;
14
20
  select(question: string, options: SelectOption[]): Promise<string>;
15
21
  printBanner(): void;
@@ -31,6 +37,11 @@ export declare class TerminalUI {
31
37
  streamText(text: string): void;
32
38
  endStreaming(): void;
33
39
  prompt(): Promise<string>;
40
+ promptWithMode(defaultMode?: ExecutionMode): Promise<{
41
+ text: string;
42
+ mode: ExecutionMode;
43
+ }>;
44
+ printModeInfo(mode: ExecutionMode): void;
34
45
  close(): void;
35
46
  clearScreen(): void;
36
47
  }
@@ -5,20 +5,28 @@ export class TerminalUI {
5
5
  rl;
6
6
  isStreaming = false;
7
7
  thinkingSpinner = null;
8
+ toolSpinner = null;
8
9
  constructor() {
9
10
  this.rl = readline.createInterface({
10
11
  input: process.stdin,
11
12
  output: process.stdout,
12
13
  });
13
14
  }
14
- // Thinking indicator
15
- startThinking(message = 'Thinking') {
15
+ // Thinking indicator with turn info
16
+ startThinking(message = 'Thinking', turn) {
17
+ const turnInfo = turn ? chalk.gray(` [Turn ${turn}]`) : '';
16
18
  this.thinkingSpinner = ora({
17
- text: chalk.cyan(message),
19
+ text: chalk.cyan(message) + turnInfo,
18
20
  spinner: 'dots',
19
21
  discardStdin: false, // Don't interfere with readline
20
22
  }).start();
21
23
  }
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
+ }
22
30
  stopThinking() {
23
31
  if (this.thinkingSpinner) {
24
32
  this.thinkingSpinner.stop();
@@ -29,6 +37,32 @@ export class TerminalUI {
29
37
  process.stdin.resume();
30
38
  }
31
39
  }
40
+ // Tool execution spinner
41
+ startToolSpinner(toolName, detail) {
42
+ this.toolSpinner = ora({
43
+ text: chalk.yellow(`${toolName}: `) + chalk.gray(detail),
44
+ spinner: 'dots',
45
+ discardStdin: false,
46
+ }).start();
47
+ }
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) {
55
+ if (this.toolSpinner) {
56
+ this.toolSpinner.fail(chalk.red(message));
57
+ this.toolSpinner = null;
58
+ }
59
+ }
60
+ stopToolSpinner() {
61
+ if (this.toolSpinner) {
62
+ this.toolSpinner.stop();
63
+ this.toolSpinner = null;
64
+ }
65
+ }
32
66
  // Confirmation prompt for dangerous actions
33
67
  async confirm(message) {
34
68
  // Ensure any spinner is stopped
@@ -78,12 +112,18 @@ export class TerminalUI {
78
112
  console.log(chalk.gray(' lighting, animations, and more - just describe what'));
79
113
  console.log(chalk.gray(' you want in plain English.'));
80
114
  console.log();
115
+ console.log(chalk.gray(' Modes: ') + chalk.cyan('⚡ Quick') + chalk.gray(' | ') + chalk.magenta('📋 Plan') + chalk.gray(' (Tab to switch)'));
81
116
  console.log(chalk.gray(' Commands: ') + chalk.yellow('/help') + chalk.gray(' | ') + chalk.yellow('/clear') + chalk.gray(' | ') + chalk.yellow('/exit'));
82
117
  console.log();
83
118
  console.log(chalk.gray(' ─────────────────────────────────────────'));
84
119
  console.log();
85
120
  }
86
121
  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'));
87
127
  console.log();
88
128
  console.log(chalk.yellow('Commands:'));
89
129
  console.log(chalk.cyan(' /help') + chalk.gray(' - Show this help message'));
@@ -176,6 +216,100 @@ export class TerminalUI {
176
216
  });
177
217
  });
178
218
  }
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
+ }
179
313
  close() {
180
314
  this.rl.close();
181
315
  }
@@ -12,27 +12,33 @@ 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
15
+ // Ask for project description with mode toggle
16
16
  console.log();
17
- console.log(chalk.cyan(' Now describe what you\'d like to build:'));
17
+ console.log(chalk.cyan(' 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 description = await ui.prompt();
20
+ const { text: description, mode } = await ui.promptWithMode('single-shot');
21
21
  console.log();
22
+ ui.printModeInfo(mode);
22
23
  console.log(chalk.gray(' ─────────────────────────────────────────'));
23
24
  console.log();
24
25
  return {
25
26
  language,
26
27
  target,
27
28
  description,
29
+ mode,
28
30
  };
29
31
  }
30
32
  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.`;
31
36
  return `The user wants to create a Three.js project with these preferences:
32
37
  - Language: ${prefs.language}
33
38
  - Target platform: ${prefs.target}
39
+ - Execution mode: ${prefs.mode === 'planning' ? 'Planning Mode (detailed plan first)' : 'Single-Shot Mode (direct implementation)'}
34
40
 
35
- Their project description: ${prefs.description}
41
+ Their project description: ${prefs.description}${modeInstruction}
36
42
 
37
43
  Please create the project structure and initial files based on these requirements. Start by setting up the basic Three.js scene.`;
38
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threewzrd",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "AI-powered CLI for generating Three.js projects from natural language",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {