orquesta-cli 0.2.111 → 0.2.115

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.
@@ -30,6 +30,7 @@ Available commands:
30
30
 
31
31
  Keyboard shortcuts:
32
32
  Ctrl+C - Exit
33
+ Ctrl+S - Send now / steer the running task (no wait for next turn)
33
34
  Ctrl+T - Toggle TODO details
34
35
  ESC - Interrupt current execution
35
36
  @ - File browser
@@ -91,7 +91,7 @@ export const TodoPanel = React.memo(({ todos, currentTodoId, isProcessing = fals
91
91
  React.createElement(Box, null,
92
92
  React.createElement(Box, { width: 2 }, isInProgress && isProcessing ? (React.createElement(Text, { color: "blueBright" },
93
93
  React.createElement(Spinner, { type: "dots2" }))) : (React.createElement(Text, { color: config.color }, config.icon))),
94
- React.createElement(Text, { color: isCompleted ? 'gray' : isInProgress ? 'white' : 'gray', bold: isInProgress, dimColor: isCompleted, strikethrough: isCompleted }, todo.title),
94
+ React.createElement(Text, { color: isCompleted ? 'gray' : isInProgress ? 'white' : 'gray', bold: isInProgress, strikethrough: isCompleted }, todo.title),
95
95
  isInProgress && isProcessing && (React.createElement(Text, { color: "blueBright" }, " \u2190"))),
96
96
  todo.error && (React.createElement(Box, { marginLeft: 2 },
97
97
  React.createElement(Text, { color: "red", dimColor: true },
@@ -86,9 +86,9 @@ export const ActivityIndicator = ({ activity, startTime, detail, subActivities =
86
86
  formatTime(elapsedSeconds)),
87
87
  renderProgressBar()),
88
88
  detail && (React.createElement(Box, { marginLeft: 2 },
89
- React.createElement(Text, { color: "gray", dimColor: true }, detail))),
89
+ React.createElement(Text, { color: "cyan" }, detail))),
90
90
  stepName && (React.createElement(Box, { marginLeft: 2 },
91
- React.createElement(Text, { color: "gray", dimColor: true },
91
+ React.createElement(Text, { color: "gray" },
92
92
  currentStep,
93
93
  "/",
94
94
  totalSteps,
@@ -98,15 +98,15 @@ export const ActivityIndicator = ({ activity, startTime, detail, subActivities =
98
98
  const subInfo = ACTIVITY_INFO[sub.type];
99
99
  return (React.createElement(Box, { key: idx },
100
100
  getStatusIcon(sub.status),
101
- React.createElement(Text, { color: "gray", dimColor: true },
101
+ React.createElement(Text, { color: "gray" },
102
102
  " ",
103
103
  subInfo.label),
104
- sub.detail && React.createElement(Text, { color: "gray", dimColor: true },
104
+ sub.detail && React.createElement(Text, { color: "white" },
105
105
  ": ",
106
106
  sub.detail)));
107
107
  }))),
108
108
  tokenCount !== undefined && (React.createElement(Box, { marginLeft: 2 },
109
- React.createElement(Text, { color: "gray", dimColor: true },
109
+ React.createElement(Text, { color: "gray" },
110
110
  formatTokens(tokenCount),
111
111
  " tokens",
112
112
  tokensPerSecond !== undefined && tokensPerSecond > 0 && ` · ${tokensPerSecond.toFixed(0)} tok/s`)))));
@@ -3,6 +3,7 @@ interface CustomTextInputProps {
3
3
  value: string;
4
4
  onChange: (value: string) => void;
5
5
  onSubmit?: (value: string) => void;
6
+ onCtrlS?: (value: string) => void;
6
7
  onHistoryPrev?: () => void;
7
8
  onHistoryNext?: () => void;
8
9
  placeholder?: string;
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useEffect, useRef } from 'react';
2
2
  import { Box, Text, useStdin } from 'ink';
3
3
  const MULTILINE_COLLAPSE_THRESHOLD = 10;
4
- export const CustomTextInput = ({ value, onChange, onSubmit, onHistoryPrev, onHistoryNext, placeholder = '', focus = true, }) => {
4
+ export const CustomTextInput = ({ value, onChange, onSubmit, onCtrlS, onHistoryPrev, onHistoryNext, placeholder = '', focus = true, }) => {
5
5
  const { stdin, setRawMode } = useStdin();
6
6
  const [cursorPosition, setCursorPosition] = useState(value.length);
7
7
  const previousValueLength = useRef(value.length);
@@ -10,6 +10,7 @@ export const CustomTextInput = ({ value, onChange, onSubmit, onHistoryPrev, onHi
10
10
  const cursorPositionRef = useRef(cursorPosition);
11
11
  const onChangeRef = useRef(onChange);
12
12
  const onSubmitRef = useRef(onSubmit);
13
+ const onCtrlSRef = useRef(onCtrlS);
13
14
  const onHistoryPrevRef = useRef(onHistoryPrev);
14
15
  const onHistoryNextRef = useRef(onHistoryNext);
15
16
  const setIsCollapsedViewRef = useRef(setIsCollapsedView);
@@ -19,6 +20,7 @@ export const CustomTextInput = ({ value, onChange, onSubmit, onHistoryPrev, onHi
19
20
  cursorPositionRef.current = cursorPosition;
20
21
  onChangeRef.current = onChange;
21
22
  onSubmitRef.current = onSubmit;
23
+ onCtrlSRef.current = onCtrlS;
22
24
  onHistoryPrevRef.current = onHistoryPrev;
23
25
  onHistoryNextRef.current = onHistoryNext;
24
26
  setIsCollapsedViewRef.current = setIsCollapsedView;
@@ -49,6 +51,10 @@ export const CustomTextInput = ({ value, onChange, onSubmit, onHistoryPrev, onHi
49
51
  const str = data.toString();
50
52
  const currentValue = valueRef.current;
51
53
  const currentCursor = cursorPositionRef.current;
54
+ if (str === '\x13') {
55
+ onCtrlSRef.current?.(currentValue);
56
+ return;
57
+ }
52
58
  const deleteWordBeforeCursor = (value, cursor) => {
53
59
  if (cursor > 0) {
54
60
  const beforeCursor = value.slice(0, cursor);
@@ -50,7 +50,7 @@ const require = createRequire(import.meta.url);
50
50
  const pkg = require('../../../package.json');
51
51
  const VERSION = pkg.version;
52
52
  const TOOLS_REQUIRING_APPROVAL = new Set(['create_file', 'edit_file', 'bash']);
53
- const STARTUP_TIP = 'Type /help for available commands. Type /model to switch models.';
53
+ const STARTUP_TIP = 'Type /help for commands, /model to switch models. Press Ctrl+S to send now (or steer the AI mid-task).';
54
54
  function shortenPath(fullPath) {
55
55
  const homeDir = os.homedir();
56
56
  if (fullPath.startsWith(homeDir)) {
@@ -641,6 +641,32 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
641
641
  setShowLogFiles(prev => !prev);
642
642
  }
643
643
  });
644
+ const handleCtrlS = useCallback((rawValue) => {
645
+ if (showSessionBrowser ||
646
+ showSettings ||
647
+ showSetupWizard ||
648
+ fileBrowserState.showFileBrowser ||
649
+ commandBrowserState.showCommandBrowser ||
650
+ planExecutionState.askUserRequest ||
651
+ pendingToolApproval) {
652
+ return;
653
+ }
654
+ const text = (rawValue ?? '').trim();
655
+ if (!text)
656
+ return;
657
+ if (isProcessing) {
658
+ addLog({ type: 'user_input', content: `(⚡ Ctrl+S — steering now, picked up at next step) ${text}` });
659
+ setPendingUserMessage(text);
660
+ setInput('');
661
+ }
662
+ else {
663
+ handleSubmitRef.current?.(text);
664
+ }
665
+ }, [
666
+ showSessionBrowser, showSettings, showSetupWizard,
667
+ fileBrowserState.showFileBrowser, commandBrowserState.showCommandBrowser,
668
+ planExecutionState.askUserRequest, pendingToolApproval, isProcessing, addLog,
669
+ ]);
644
670
  const handleFileSelect = useCallback((filePaths) => {
645
671
  logger.debug('File selected', { filePaths });
646
672
  const newInput = fileBrowserState.handleFileSelect(filePaths, input);
@@ -1325,6 +1351,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
1325
1351
  : reason;
1326
1352
  return (React.createElement(Box, { key: entry.id, flexDirection: "column", marginTop: 1 },
1327
1353
  React.createElement(Box, null,
1354
+ React.createElement(Text, { color: "gray", dimColor: true }, "\u2502 "),
1328
1355
  React.createElement(Text, { color: "cyan", bold: true },
1329
1356
  icon,
1330
1357
  " ",
@@ -1398,9 +1425,11 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
1398
1425
  ". ",
1399
1426
  item))))));
1400
1427
  case 'todo_start':
1401
- return (React.createElement(Box, { key: entry.id, marginTop: 1 },
1402
- React.createElement(Text, { color: "blue", bold: true }, "\uD83C\uDFB5 \u25B6 "),
1403
- React.createElement(Text, { bold: true }, entry.content)));
1428
+ return (React.createElement(Box, { key: entry.id, flexDirection: "column", marginTop: 1 },
1429
+ React.createElement(Text, { color: "gray", dimColor: true }, '─'.repeat(44)),
1430
+ React.createElement(Box, null,
1431
+ React.createElement(Text, { color: "blue", bold: true }, "\uD83C\uDFB5 \u25B6 "),
1432
+ React.createElement(Text, { bold: true }, entry.content))));
1404
1433
  case 'todo_complete':
1405
1434
  return (React.createElement(Box, { key: entry.id, marginLeft: 2 },
1406
1435
  React.createElement(Text, { color: "white" }, "\u23BF "),
@@ -1466,7 +1495,7 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
1466
1495
  React.createElement(ActivityIndicator, { activity: getCurrentActivityType(), startTime: activityStartTime, detail: activityDetail || currentToolName || '', subActivities: subActivities, modelName: currentModelInfo.model }),
1467
1496
  streamingText && (React.createElement(StreamingOutput, { text: streamingText })))),
1468
1497
  isDocsSearching && (React.createElement(DocsSearchProgress, { logs: docsSearchLogs, isSearching: isDocsSearching })),
1469
- planExecutionState.todos.length > 0 && (React.createElement(Box, { marginTop: 2, marginBottom: 1 },
1498
+ planExecutionState.todos.length > 0 && (React.createElement(Box, { marginTop: 1 },
1470
1499
  React.createElement(TodoPanel, { todos: planExecutionState.todos, currentTodoId: planExecutionState.currentTodoId, isProcessing: isProcessing }))),
1471
1500
  updatePhase === 'available' && (React.createElement(UpdateNotification, { currentVersion: VERSION, latestVersion: updateLatest })),
1472
1501
  updatePhase === 'updating' && (React.createElement(UpdateNotification, { currentVersion: VERSION, latestVersion: updateLatest, isUpdating: true, updateProgress: updateProgress })),
@@ -1485,16 +1514,19 @@ export const PlanExecuteApp = ({ llmClient: initialLlmClient, modelInfo, resumeL
1485
1514
  return;
1486
1515
  }
1487
1516
  setInput(value);
1488
- }, onSubmit: handleSubmit, onHistoryPrev: isAnyPanelOpen ? undefined : handleHistoryPrev, onHistoryNext: isAnyPanelOpen ? undefined : handleHistoryNext, placeholder: isProcessing
1489
- ? "AI is working..."
1517
+ }, onSubmit: handleSubmit, onCtrlS: handleCtrlS, onHistoryPrev: isAnyPanelOpen ? undefined : handleHistoryPrev, onHistoryNext: isAnyPanelOpen ? undefined : handleHistoryNext, placeholder: isProcessing
1518
+ ? "AI is working... (type + Ctrl+S to steer now)"
1490
1519
  : showSessionBrowser
1491
1520
  ? "Select a session or press ESC..."
1492
1521
  : showSettings
1493
1522
  ? "Press ESC to close settings..."
1494
1523
  : showDocsBrowser
1495
1524
  ? "Select a doc source or press ESC..."
1496
- : "Type your message... (@ files, / commands, Alt+Enter newline)", focus: !showSessionBrowser && !showSettings && !showDocsBrowser && !planExecutionState.askUserRequest && updatePhase === 'hidden' })),
1525
+ : "Type your message... (@ files, / commands, Alt+Enter newline, Ctrl+S send)", focus: !showSessionBrowser && !showSettings && !showDocsBrowser && !planExecutionState.askUserRequest && updatePhase === 'hidden' })),
1497
1526
  input.length > 0 && (React.createElement(Text, { color: input.length > 4000 ? 'red' : input.length > 2000 ? 'yellow' : 'gray', dimColor: true }, input.length.toLocaleString())))),
1527
+ isProcessing && input.trim().length > 0 && (React.createElement(Box, { paddingX: 1 },
1528
+ React.createElement(Text, { color: "yellow", bold: true }, "\u26A1 Ctrl+S"),
1529
+ React.createElement(Text, { color: "gray" }, " to send now, or Enter to queue for the next step"))),
1498
1530
  fileBrowserState.showFileBrowser && !isProcessing && (React.createElement(Box, { marginTop: 0 }, fileBrowserState.isLoadingFiles ? (React.createElement(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1 },
1499
1531
  React.createElement(Spinner, { type: "dots" }),
1500
1532
  React.createElement(Text, { color: "yellow" }, " Loading files..."))) : (React.createElement(FileBrowser, { filter: fileBrowserState.filterText, onSelect: handleFileSelect, onCancel: fileBrowserState.handleFileBrowserCancel, cachedFiles: fileBrowserState.cachedFileList })))),
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  interface StreamingOutputProps {
3
3
  text: string;
4
+ height?: number;
4
5
  }
5
6
  export declare const StreamingOutput: React.FC<StreamingOutputProps>;
6
7
  export {};
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import { Box, Text } from 'ink';
3
- export const StreamingOutput = ({ text }) => {
3
+ export const StreamingOutput = ({ text, height = 3 }) => {
4
4
  if (!text)
5
5
  return null;
6
- return (React.createElement(Box, { marginTop: 1, paddingX: 1 },
7
- React.createElement(Text, { wrap: "wrap" }, text.split('\n').slice(-8).join('\n'))));
6
+ const tail = text.split('\n').slice(-height);
7
+ const pad = Array(Math.max(0, height - tail.length)).fill('');
8
+ const rows = [...pad, ...tail];
9
+ return (React.createElement(Box, { paddingX: 1, flexDirection: "column", height: height }, rows.map((line, i) => (React.createElement(Text, { key: i, wrap: "truncate-end" }, line || ' ')))));
8
10
  };
9
11
  //# sourceMappingURL=StreamingOutput.js.map
@@ -1,13 +1,30 @@
1
1
  #!/usr/bin/env node
2
2
  import React from 'react';
3
+ import { execSync } from 'child_process';
3
4
  import { render } from 'ink';
4
5
  import { PlanExecuteApp } from './components/PlanExecuteApp.js';
5
6
  import { createLLMClient } from '../core/llm/llm-client.js';
6
7
  import { configManager } from '../core/config/config-manager.js';
7
8
  import { initializeOptionalTools } from '../tools/registry.js';
8
9
  import { logger } from '../utils/logger.js';
10
+ function disableFlowControl() {
11
+ if (process.platform === 'win32' || !process.stdin.isTTY)
12
+ return;
13
+ try {
14
+ execSync('stty -ixon', { stdio: 'inherit' });
15
+ process.on('exit', () => {
16
+ try {
17
+ execSync('stty ixon', { stdio: 'inherit' });
18
+ }
19
+ catch { }
20
+ });
21
+ }
22
+ catch {
23
+ }
24
+ }
9
25
  (async () => {
10
26
  try {
27
+ disableFlowControl();
11
28
  await configManager.initialize();
12
29
  await initializeOptionalTools();
13
30
  const llmClient = createLLMClient();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orquesta-cli",
3
- "version": "0.2.111",
3
+ "version": "0.2.115",
4
4
  "description": "Orquesta CLI - AI-powered coding assistant with team collaboration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",