prompt-language-shell 0.4.4 → 0.4.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.
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { ComponentName } from '../types/types.js';
3
- import { AnthropicModel, isValidAnthropicApiKey, isValidAnthropicModel, } from './configuration.js';
3
+ import { getConfigSchema, loadConfig, } from './configuration.js';
4
4
  import { getConfirmationMessage } from './messages.js';
5
5
  import { StepType } from '../ui/Config.js';
6
6
  export function markAsDone(component) {
@@ -14,27 +14,132 @@ export function createWelcomeDefinition(app) {
14
14
  };
15
15
  }
16
16
  export function createConfigSteps() {
17
- return [
18
- {
19
- description: 'Anthropic API key',
20
- key: 'key',
21
- type: StepType.Text,
22
- value: null,
23
- validate: isValidAnthropicApiKey,
24
- },
25
- {
26
- description: 'Model',
27
- key: 'model',
28
- type: StepType.Selection,
29
- options: [
30
- { label: 'Haiku 4.5', value: AnthropicModel.Haiku },
31
- { label: 'Sonnet 4.5', value: AnthropicModel.Sonnet },
32
- { label: 'Opus 4.1', value: AnthropicModel.Opus },
33
- ],
34
- defaultIndex: 0,
35
- validate: isValidAnthropicModel,
36
- },
37
- ];
17
+ // Use schema-based config step generation for required Anthropic settings
18
+ return createConfigStepsFromSchema(['anthropic.key', 'anthropic.model']);
19
+ }
20
+ /**
21
+ * Get current config value for a dotted key path
22
+ */
23
+ function getConfigValue(config, key) {
24
+ if (!config)
25
+ return undefined;
26
+ const parts = key.split('.');
27
+ let value = config;
28
+ for (const part of parts) {
29
+ if (value && typeof value === 'object' && part in value) {
30
+ value = value[part];
31
+ }
32
+ else {
33
+ return undefined;
34
+ }
35
+ }
36
+ return value;
37
+ }
38
+ /**
39
+ * Get validation function for a config definition
40
+ */
41
+ function getValidator(definition) {
42
+ switch (definition.type) {
43
+ case 'regexp':
44
+ return (value) => definition.pattern.test(value);
45
+ case 'string':
46
+ return () => true; // Strings are always valid
47
+ case 'enum':
48
+ return (value) => definition.values.includes(value);
49
+ case 'number':
50
+ return (value) => !isNaN(Number(value));
51
+ case 'boolean':
52
+ return (value) => value === 'true' || value === 'false';
53
+ }
54
+ }
55
+ /**
56
+ * Create config steps from schema for specified keys
57
+ */
58
+ export function createConfigStepsFromSchema(keys) {
59
+ const schema = getConfigSchema();
60
+ let currentConfig = null;
61
+ try {
62
+ currentConfig = loadConfig();
63
+ }
64
+ catch {
65
+ // Config doesn't exist yet, use defaults
66
+ }
67
+ return keys.map((key) => {
68
+ if (!(key in schema)) {
69
+ throw new Error(`Unknown config key: ${key}`);
70
+ }
71
+ const definition = schema[key];
72
+ const currentValue = getConfigValue(currentConfig, key);
73
+ const keyParts = key.split('.');
74
+ const shortKey = keyParts[keyParts.length - 1];
75
+ // Map definition to ConfigStep based on type
76
+ switch (definition.type) {
77
+ case 'regexp':
78
+ case 'string': {
79
+ const value = currentValue !== undefined && typeof currentValue === 'string'
80
+ ? currentValue
81
+ : definition.type === 'string'
82
+ ? (definition.default ?? '')
83
+ : null;
84
+ return {
85
+ description: definition.description,
86
+ key: shortKey,
87
+ type: StepType.Text,
88
+ value,
89
+ validate: getValidator(definition),
90
+ };
91
+ }
92
+ case 'number': {
93
+ const value = currentValue !== undefined && typeof currentValue === 'number'
94
+ ? String(currentValue)
95
+ : definition.default !== undefined
96
+ ? String(definition.default)
97
+ : '0';
98
+ return {
99
+ description: definition.description,
100
+ key: shortKey,
101
+ type: StepType.Text,
102
+ value,
103
+ validate: getValidator(definition),
104
+ };
105
+ }
106
+ case 'enum': {
107
+ const currentStr = currentValue !== undefined && typeof currentValue === 'string'
108
+ ? currentValue
109
+ : definition.default;
110
+ const defaultIndex = currentStr
111
+ ? definition.values.indexOf(currentStr)
112
+ : 0;
113
+ return {
114
+ description: definition.description,
115
+ key: shortKey,
116
+ type: StepType.Selection,
117
+ options: definition.values.map((value) => ({
118
+ label: value,
119
+ value,
120
+ })),
121
+ defaultIndex: Math.max(0, defaultIndex),
122
+ validate: getValidator(definition),
123
+ };
124
+ }
125
+ case 'boolean': {
126
+ const currentBool = currentValue !== undefined && typeof currentValue === 'boolean'
127
+ ? currentValue
128
+ : undefined;
129
+ return {
130
+ description: definition.description,
131
+ key: shortKey,
132
+ type: StepType.Selection,
133
+ options: [
134
+ { label: 'Yes', value: 'true' },
135
+ { label: 'No', value: 'false' },
136
+ ],
137
+ defaultIndex: currentBool !== undefined ? (currentBool ? 0 : 1) : 0,
138
+ validate: getValidator(definition),
139
+ };
140
+ }
141
+ }
142
+ });
38
143
  }
39
144
  export function createConfigDefinition(onFinished, onAborted) {
40
145
  return {
@@ -48,6 +153,21 @@ export function createConfigDefinition(onFinished, onAborted) {
48
153
  },
49
154
  };
50
155
  }
156
+ /**
157
+ * Create config definition with specific keys
158
+ */
159
+ export function createConfigDefinitionWithKeys(keys, onFinished, onAborted) {
160
+ return {
161
+ id: randomUUID(),
162
+ name: ComponentName.Config,
163
+ state: { done: false },
164
+ props: {
165
+ steps: createConfigStepsFromSchema(keys),
166
+ onFinished,
167
+ onAborted,
168
+ },
169
+ };
170
+ }
51
171
  export function createCommandDefinition(command, service, onError, onComplete, onAborted) {
52
172
  return {
53
173
  id: randomUUID(),
@@ -166,3 +166,83 @@ export function getConfigurationRequiredMessage(forFutureUse = false) {
166
166
  ];
167
167
  return messages[Math.floor(Math.random() * messages.length)];
168
168
  }
169
+ /**
170
+ * Core configuration schema - defines structure and types for built-in settings
171
+ */
172
+ const coreConfigSchema = {
173
+ 'anthropic.key': {
174
+ type: 'regexp',
175
+ required: true,
176
+ pattern: /^sk-ant-api03-[A-Za-z0-9_-]{95}$/,
177
+ description: 'Anthropic API key',
178
+ },
179
+ 'anthropic.model': {
180
+ type: 'enum',
181
+ required: true,
182
+ values: SUPPORTED_MODELS,
183
+ default: AnthropicModel.Haiku,
184
+ description: 'Anthropic model',
185
+ },
186
+ 'settings.debug': {
187
+ type: 'boolean',
188
+ required: false,
189
+ description: 'Debug mode',
190
+ },
191
+ };
192
+ /**
193
+ * Get complete configuration schema
194
+ * Currently returns core schema only
195
+ * Future: will merge with skill-declared schemas
196
+ */
197
+ export function getConfigSchema() {
198
+ return {
199
+ ...coreConfigSchema,
200
+ // Future: ...loadSkillSchemas()
201
+ };
202
+ }
203
+ /**
204
+ * Get available config structure for CONFIG tool
205
+ * Returns keys with descriptions only (no values for privacy)
206
+ */
207
+ export function getAvailableConfigStructure() {
208
+ const schema = getConfigSchema();
209
+ const structure = {};
210
+ // Add core schema keys with descriptions
211
+ for (const [key, definition] of Object.entries(schema)) {
212
+ structure[key] = definition.description;
213
+ }
214
+ // Add discovered keys from config file (if it exists)
215
+ try {
216
+ const configFile = getConfigFile();
217
+ if (!existsSync(configFile)) {
218
+ return structure;
219
+ }
220
+ const content = readFileSync(configFile, 'utf-8');
221
+ const parsed = YAML.parse(content);
222
+ // Flatten nested config to dot notation
223
+ function flattenConfig(obj, prefix = '') {
224
+ const result = {};
225
+ for (const [key, value] of Object.entries(obj)) {
226
+ const fullKey = prefix ? `${prefix}.${key}` : key;
227
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
228
+ Object.assign(result, flattenConfig(value, fullKey));
229
+ }
230
+ else {
231
+ result[fullKey] = value;
232
+ }
233
+ }
234
+ return result;
235
+ }
236
+ const flatConfig = flattenConfig(parsed);
237
+ // Add discovered keys that aren't in schema
238
+ for (const key of Object.keys(flatConfig)) {
239
+ if (!structure[key]) {
240
+ structure[key] = `${key} (discovered)`;
241
+ }
242
+ }
243
+ }
244
+ catch {
245
+ // Config file doesn't exist or can't be read, only use schema
246
+ }
247
+ return structure;
248
+ }
@@ -0,0 +1,86 @@
1
+ import { useInput as useInkInput } from 'ink';
2
+ const globalShortcuts = new Map();
3
+ /**
4
+ * Converts a Key object to a normalized pattern string
5
+ * Example: { shift: true, tab: true } → "shift+tab"
6
+ */
7
+ function keyToPattern(key) {
8
+ const modifiers = [];
9
+ if (key.ctrl)
10
+ modifiers.push('ctrl');
11
+ if (key.meta)
12
+ modifiers.push('meta');
13
+ if (key.shift)
14
+ modifiers.push('shift');
15
+ // Get the key name
16
+ let keyName = '';
17
+ if (key.escape)
18
+ keyName = 'escape';
19
+ else if (key.tab)
20
+ keyName = 'tab';
21
+ else if (key.return)
22
+ keyName = 'return';
23
+ else if (key.upArrow)
24
+ keyName = 'up';
25
+ else if (key.downArrow)
26
+ keyName = 'down';
27
+ else if (key.leftArrow)
28
+ keyName = 'left';
29
+ else if (key.rightArrow)
30
+ keyName = 'right';
31
+ else if (key.backspace)
32
+ keyName = 'backspace';
33
+ else if (key.delete)
34
+ keyName = 'delete';
35
+ if (!keyName)
36
+ return null;
37
+ return [...modifiers, keyName].join('+');
38
+ }
39
+ /**
40
+ * Register a global keyboard shortcut
41
+ * @param pattern Pattern string like "shift+tab" or "ctrl+c"
42
+ * @param handler Function to call when shortcut is triggered
43
+ */
44
+ export function registerGlobalShortcut(pattern, handler) {
45
+ globalShortcuts.set(pattern.toLowerCase(), handler);
46
+ }
47
+ /**
48
+ * Check if a key event matches a global shortcut
49
+ * If matched, calls the handler and returns true
50
+ * If not matched, returns false
51
+ * @param key The key object from ink's useInput
52
+ * @returns true if global shortcut was handled, false otherwise
53
+ */
54
+ export function isGlobalShortcut(key) {
55
+ const pattern = keyToPattern(key);
56
+ if (!pattern)
57
+ return false;
58
+ const handler = globalShortcuts.get(pattern.toLowerCase());
59
+ if (handler) {
60
+ handler();
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ /**
66
+ * Clear all registered global shortcuts (useful for testing)
67
+ */
68
+ export function clearGlobalShortcuts() {
69
+ globalShortcuts.clear();
70
+ }
71
+ /**
72
+ * Custom useInput hook that automatically handles global shortcuts
73
+ * before passing events to the component handler
74
+ * @param handler Component's keyboard event handler
75
+ * @param options Options for useInput (isActive, etc.)
76
+ */
77
+ export function useInput(handler, options) {
78
+ useInkInput((input, key) => {
79
+ // Check for global shortcuts first
80
+ if (isGlobalShortcut(key)) {
81
+ return; // Global shortcut handled, don't propagate to component
82
+ }
83
+ // No global shortcut matched, call component handler
84
+ handler(input, key);
85
+ }, options);
86
+ }
@@ -1,3 +1,4 @@
1
+ import { loadDebugSetting } from './configuration.js';
1
2
  /**
2
3
  * Returns a natural language confirmation message for plan execution.
3
4
  * Randomly selects from variations to sound less robotic.
@@ -48,3 +49,32 @@ export const FeedbackMessages = {
48
49
  ConfigurationComplete: 'Configuration complete.',
49
50
  UnexpectedError: 'Unexpected error occurred:',
50
51
  };
52
+ /**
53
+ * Extracts a user-friendly error message from API errors.
54
+ * In debug mode, returns the full error; otherwise, returns just the message.
55
+ *
56
+ * Handles Anthropic API error format:
57
+ * 400 {"type":"error","error":{"type":"...","message":"..."},"request_id":"..."}
58
+ */
59
+ export function formatErrorMessage(error) {
60
+ const rawMessage = error instanceof Error ? error.message : 'Unknown error occurred';
61
+ if (loadDebugSetting()) {
62
+ return rawMessage;
63
+ }
64
+ // Try to extract message from Anthropic API error format
65
+ // Format: "400 {json...}" or just "{json...}"
66
+ const jsonMatch = rawMessage.match(/\{.*\}/s);
67
+ if (jsonMatch) {
68
+ try {
69
+ const parsed = JSON.parse(jsonMatch[0]);
70
+ const message = parsed.error?.message ?? parsed.message;
71
+ if (message) {
72
+ return message;
73
+ }
74
+ }
75
+ catch {
76
+ // JSON parsing failed, return original message
77
+ }
78
+ }
79
+ return rawMessage;
80
+ }
@@ -34,6 +34,7 @@ class ToolRegistry {
34
34
  export const toolRegistry = new ToolRegistry();
35
35
  // Register built-in tools
36
36
  import { answerTool } from '../tools/answer.tool.js';
37
+ import { configTool } from '../tools/config.tool.js';
37
38
  import { introspectTool } from '../tools/introspect.tool.js';
38
39
  import { planTool } from '../tools/plan.tool.js';
39
40
  toolRegistry.register('plan', {
@@ -48,3 +49,7 @@ toolRegistry.register('answer', {
48
49
  schema: answerTool,
49
50
  instructionsPath: 'config/ANSWER.md',
50
51
  });
52
+ toolRegistry.register('config', {
53
+ schema: configTool,
54
+ instructionsPath: 'config/CONFIG.md',
55
+ });
@@ -0,0 +1,43 @@
1
+ export const configTool = {
2
+ name: 'config',
3
+ description: 'Determine which configuration settings to show based on user query. Receives available config keys with descriptions and returns which keys the user wants to configure.',
4
+ input_schema: {
5
+ type: 'object',
6
+ properties: {
7
+ message: {
8
+ type: 'string',
9
+ description: 'Brief message to display before config plan. Single sentence, maximum 64 characters. End with period.',
10
+ },
11
+ tasks: {
12
+ type: 'array',
13
+ description: 'Array of config settings to configure. Each task has type "config" and params with the config key.',
14
+ items: {
15
+ type: 'object',
16
+ properties: {
17
+ action: {
18
+ type: 'string',
19
+ description: 'Description of the config setting (from the provided descriptions). Maximum 64 characters.',
20
+ },
21
+ type: {
22
+ type: 'string',
23
+ description: 'Always "config" for configuration tasks.',
24
+ },
25
+ params: {
26
+ type: 'object',
27
+ description: 'Parameters for the config task.',
28
+ properties: {
29
+ key: {
30
+ type: 'string',
31
+ description: 'The config key to configure (e.g., "anthropic.key", "settings.debug").',
32
+ },
33
+ },
34
+ required: ['key'],
35
+ },
36
+ },
37
+ required: ['action', 'type', 'params'],
38
+ },
39
+ },
40
+ },
41
+ required: ['message', 'tasks'],
42
+ },
43
+ };
package/dist/ui/Answer.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import { getTextColor } from '../services/colors.js';
3
+ import { Box, Text } from 'ink';
4
+ import { Colors, getTextColor } from '../services/colors.js';
5
+ import { useInput } from '../services/keyboard.js';
6
+ import { formatErrorMessage } from '../services/messages.js';
5
7
  import { Spinner } from './Spinner.js';
6
- const MIN_PROCESSING_TIME = 1000;
8
+ const MINIMUM_PROCESSING_TIME = 400;
7
9
  export function Answer({ question, state, service, onError, onComplete, onAborted, }) {
8
10
  const done = state?.done ?? false;
9
11
  const isCurrent = done === false;
@@ -33,7 +35,7 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
33
35
  // Call answer tool
34
36
  const result = await svc.processWithTool(question, 'answer');
35
37
  const elapsed = Date.now() - startTime;
36
- const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
38
+ const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
37
39
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
38
40
  if (mounted) {
39
41
  // Extract answer from result
@@ -44,10 +46,10 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
44
46
  }
45
47
  catch (err) {
46
48
  const elapsed = Date.now() - startTime;
47
- const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
49
+ const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
48
50
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
49
51
  if (mounted) {
50
- const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
52
+ const errorMessage = formatErrorMessage(err);
51
53
  setIsLoading(false);
52
54
  if (onError) {
53
55
  onError(errorMessage);
@@ -67,5 +69,5 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
67
69
  if (done || (!isLoading && !error)) {
68
70
  return null;
69
71
  }
70
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Finding answer. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) }))] }));
72
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [isLoading && (_jsxs(Box, { children: [_jsx(Text, { color: getTextColor(isCurrent), children: "Finding answer. " }), _jsx(Spinner, {})] })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) }))] }));
71
73
  }
@@ -1,7 +1,10 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import { getTextColor } from '../services/colors.js';
3
+ import { Box, Text } from 'ink';
4
+ import { TaskType } from '../types/types.js';
5
+ import { Colors } from '../services/colors.js';
6
+ import { useInput } from '../services/keyboard.js';
7
+ import { formatErrorMessage } from '../services/messages.js';
5
8
  import { Spinner } from './Spinner.js';
6
9
  const MIN_PROCESSING_TIME = 1000; // purely for visual effect
7
10
  export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
@@ -29,7 +32,16 @@ export function Command({ command, state, service, children, onError, onComplete
29
32
  async function process(svc) {
30
33
  const startTime = Date.now();
31
34
  try {
32
- const result = await svc.processWithTool(command, 'plan');
35
+ let result = await svc.processWithTool(command, 'plan');
36
+ // If all tasks are config type, delegate to CONFIG tool
37
+ const allConfig = result.tasks.length > 0 &&
38
+ result.tasks.every((task) => task.type === TaskType.Config);
39
+ if (allConfig) {
40
+ // Extract query from first config task params, default to 'app'
41
+ const query = result.tasks[0].params?.query || 'app';
42
+ // Call CONFIG tool to get specific config keys
43
+ result = await svc.processWithTool(query, 'config');
44
+ }
33
45
  const elapsed = Date.now() - startTime;
34
46
  const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
35
47
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
@@ -43,7 +55,7 @@ export function Command({ command, state, service, children, onError, onComplete
43
55
  const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
44
56
  await new Promise((resolve) => setTimeout(resolve, remainingTime));
45
57
  if (mounted) {
46
- const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';
58
+ const errorMessage = formatErrorMessage(err);
47
59
  setIsLoading(false);
48
60
  if (onError) {
49
61
  onError(errorMessage);
@@ -60,5 +72,5 @@ export function Command({ command, state, service, children, onError, onComplete
60
72
  };
61
73
  }, [command, done, service]);
62
74
  const isCurrent = done === false;
63
- return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: getTextColor(isCurrent), children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), children] }));
75
+ return (_jsxs(Box, { alignSelf: "flex-start", flexDirection: "column", children: [_jsxs(Box, { paddingX: done ? 1 : 0, marginX: done ? -1 : 0, backgroundColor: done ? Colors.Background.UserQuery : undefined, children: [_jsxs(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.UserQuery, children: ["> pls ", command] }), isLoading && (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Spinner, {})] }))] }), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: Colors.Status.Error, children: ["Error: ", error] }) })), children] }));
64
76
  }
@@ -49,7 +49,7 @@ export const Component = React.memo(function Component({ def, debug, }) {
49
49
  case ComponentName.Introspect: {
50
50
  const props = def.props;
51
51
  const state = def.state;
52
- return _jsx(Introspect, { ...props, state: state });
52
+ return _jsx(Introspect, { ...props, state: state, debug: debug });
53
53
  }
54
54
  case ComponentName.Report:
55
55
  return _jsx(Report, { ...def.props });
package/dist/ui/Config.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { Box, Text, useFocus, useInput } from 'ink';
3
+ import { Box, Text, useFocus } from 'ink';
4
4
  import TextInput from 'ink-text-input';
5
5
  import { Colors } from '../services/colors.js';
6
+ import { useInput } from '../services/keyboard.js';
6
7
  export var StepType;
7
8
  (function (StepType) {
8
9
  StepType["Text"] = "text";
@@ -20,11 +21,13 @@ function TextStep({ value, placeholder, validate, onChange, onSubmit, }) {
20
21
  }
21
22
  };
22
23
  const handleSubmit = (value) => {
23
- if (!validate(value)) {
24
+ // Use placeholder if input is empty
25
+ const finalValue = value || placeholder || '';
26
+ if (!validate(finalValue)) {
24
27
  setValidationFailed(true);
25
28
  return;
26
29
  }
27
- onSubmit(value);
30
+ onSubmit(finalValue);
28
31
  };
29
32
  // Handle input manually when validation fails
30
33
  useInput((input, key) => {
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { Box, Text, useInput } from 'ink';
3
+ import { Box, Text } from 'ink';
4
4
  import { Colors } from '../services/colors.js';
5
+ import { useInput } from '../services/keyboard.js';
5
6
  export function Confirm({ message, state, onConfirmed, onCancelled, }) {
6
7
  const done = state?.done ?? false;
7
8
  const isCurrent = done === false;
@@ -29,15 +30,15 @@ export function Confirm({ message, state, onConfirmed, onCancelled, }) {
29
30
  }
30
31
  }, { isActive: !done });
31
32
  const options = [
32
- { label: 'Yes', value: 'yes', color: Colors.Action.Execute },
33
- { label: 'No', value: 'no', color: Colors.Action.Discard },
33
+ { label: 'yes', value: 'yes', color: Colors.Action.Execute },
34
+ { label: 'no', value: 'no', color: Colors.Status.Error },
34
35
  ];
35
36
  if (done) {
36
37
  // When done, show both the message and user's choice in timeline
37
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { children: _jsxs(Text, { color: Colors.Text.Inactive, children: ["> ", options[selectedIndex].label] }) })] }));
38
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: undefined, children: message }) }), _jsx(Box, { paddingX: 1, marginX: -1, alignSelf: "flex-start", backgroundColor: Colors.Background.UserQuery, children: _jsxs(Text, { color: Colors.Text.UserQuery, children: ["> ", options[selectedIndex].label] }) })] }));
38
39
  }
39
40
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: isCurrent ? Colors.Text.Active : Colors.Text.Inactive, children: message }) }), _jsxs(Box, { children: [_jsx(Text, { color: Colors.Action.Select, children: ">" }), _jsx(Text, { children: " " }), _jsx(Box, { children: options.map((option, index) => {
40
41
  const isSelected = index === selectedIndex;
41
- return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, bold: isSelected, children: option.label }) }, option.value));
42
+ return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { color: isSelected ? option.color : undefined, dimColor: !isSelected, children: option.label }) }, option.value));
42
43
  }) })] })] }));
43
44
  }