prompt-language-shell 0.4.9 → 0.5.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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Check if a string is all uppercase (variant placeholder indicator)
3
+ */
4
+ function isUpperCase(str) {
5
+ return str === str.toUpperCase() && str !== str.toLowerCase();
6
+ }
7
+ /**
8
+ * Parse placeholder from string
9
+ * Returns placeholder info or null if no valid placeholder found
10
+ */
11
+ export function parsePlaceholder(text) {
12
+ // Match {path.to.property} format
13
+ const match = text.match(/\{([^}]+)\}/);
14
+ if (!match) {
15
+ return null;
16
+ }
17
+ const original = match[0];
18
+ const pathString = match[1];
19
+ const path = pathString.split('.');
20
+ // Check if any path component is uppercase (variant placeholder)
21
+ const variantIndex = path.findIndex((part) => isUpperCase(part));
22
+ const hasVariant = variantIndex !== -1;
23
+ return {
24
+ original,
25
+ path,
26
+ hasVariant,
27
+ variantIndex: hasVariant ? variantIndex : undefined,
28
+ };
29
+ }
30
+ /**
31
+ * Extract all placeholders from text
32
+ */
33
+ export function extractPlaceholders(text) {
34
+ const placeholders = [];
35
+ const regex = /\{([^}]+)\}/g;
36
+ let match;
37
+ while ((match = regex.exec(text)) !== null) {
38
+ const pathString = match[1];
39
+ const path = pathString.split('.');
40
+ const variantIndex = path.findIndex((part) => isUpperCase(part));
41
+ const hasVariant = variantIndex !== -1;
42
+ placeholders.push({
43
+ original: match[0],
44
+ path,
45
+ hasVariant,
46
+ variantIndex: hasVariant ? variantIndex : undefined,
47
+ });
48
+ }
49
+ return placeholders;
50
+ }
51
+ /**
52
+ * Replace uppercase component in path with actual variant name
53
+ * Returns new path with variant replaced
54
+ */
55
+ export function resolveVariant(path, variantName) {
56
+ return path.map((part) => (isUpperCase(part) ? variantName : part));
57
+ }
58
+ /**
59
+ * Convert path array to dot notation string
60
+ */
61
+ export function pathToString(path) {
62
+ return path.join('.');
63
+ }
64
+ /**
65
+ * Resolve placeholder value from config
66
+ * Returns the value at the specified path or undefined if not found
67
+ */
68
+ export function resolveFromConfig(config, path) {
69
+ let current = config;
70
+ for (const part of path) {
71
+ if (current === null || current === undefined) {
72
+ return undefined;
73
+ }
74
+ if (typeof current !== 'object') {
75
+ return undefined;
76
+ }
77
+ current = current[part];
78
+ }
79
+ if (typeof current === 'string' ||
80
+ typeof current === 'boolean' ||
81
+ typeof current === 'number') {
82
+ return current;
83
+ }
84
+ return undefined;
85
+ }
86
+ /**
87
+ * Replace all placeholders in text with values from config
88
+ * Note: Variant placeholders (with uppercase components) must be resolved first
89
+ */
90
+ export function replacePlaceholders(text, config) {
91
+ return text.replace(/\{([^}]+)\}/g, (_match, pathString) => {
92
+ const path = pathString.split('.');
93
+ const value = resolveFromConfig(config, path);
94
+ if (value === undefined) {
95
+ // Keep placeholder if not found in config
96
+ return `{${pathString}}`;
97
+ }
98
+ return String(value);
99
+ });
100
+ }
101
+ /**
102
+ * Check if text contains any placeholders
103
+ */
104
+ export function hasPlaceholders(text) {
105
+ return /\{[^}]+\}/.test(text);
106
+ }
107
+ /**
108
+ * Get all unique config paths required by placeholders in text
109
+ * Note: Variant placeholders (with uppercase components) must be resolved first
110
+ */
111
+ export function getRequiredConfigPaths(text) {
112
+ const placeholders = extractPlaceholders(text);
113
+ const paths = new Set();
114
+ for (const placeholder of placeholders) {
115
+ if (!placeholder.hasVariant) {
116
+ paths.add(pathToString(placeholder.path));
117
+ }
118
+ }
119
+ return Array.from(paths);
120
+ }
@@ -4,6 +4,7 @@ export var ExecutionStatus;
4
4
  ExecutionStatus["Running"] = "running";
5
5
  ExecutionStatus["Success"] = "success";
6
6
  ExecutionStatus["Failed"] = "failed";
7
+ ExecutionStatus["Aborted"] = "aborted";
7
8
  })(ExecutionStatus || (ExecutionStatus = {}));
8
9
  export var ExecutionResult;
9
10
  (function (ExecutionResult) {
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Parse skill reference from execution line
3
+ * Returns skill name if line is a reference, otherwise null
4
+ */
5
+ export function parseSkillReference(line) {
6
+ const match = line.match(/^\[(.+)\]$/);
7
+ return match ? match[1].trim() : null;
8
+ }
9
+ /**
10
+ * Check if execution line is a skill reference
11
+ */
12
+ export function isSkillReference(line) {
13
+ return /^\[.+\]$/.test(line.trim());
14
+ }
15
+ /**
16
+ * Expand skill references in execution commands
17
+ * Returns expanded execution lines with references replaced
18
+ * Throws error if circular reference detected
19
+ */
20
+ export function expandSkillReferences(execution, skillLookup, visited = new Set()) {
21
+ const expanded = [];
22
+ for (const line of execution) {
23
+ const skillName = parseSkillReference(line);
24
+ if (!skillName) {
25
+ // Not a reference, keep as-is
26
+ expanded.push(line);
27
+ continue;
28
+ }
29
+ // Check for circular reference
30
+ if (visited.has(skillName)) {
31
+ throw new Error(`Circular skill reference detected: ${Array.from(visited).join(' → ')} → ${skillName}`);
32
+ }
33
+ // Look up referenced skill
34
+ const skill = skillLookup(skillName);
35
+ if (!skill) {
36
+ // Referenced skill not found, keep as-is
37
+ expanded.push(line);
38
+ continue;
39
+ }
40
+ if (!skill.execution || skill.execution.length === 0) {
41
+ // Referenced skill has no execution, skip
42
+ continue;
43
+ }
44
+ // Recursively expand referenced skill's execution
45
+ const newVisited = new Set(visited);
46
+ newVisited.add(skillName);
47
+ const referencedExecution = expandSkillReferences(skill.execution, skillLookup, newVisited);
48
+ expanded.push(...referencedExecution);
49
+ }
50
+ return expanded;
51
+ }
52
+ /**
53
+ * Get all skill names referenced in execution (including nested)
54
+ * Returns unique set of skill names
55
+ */
56
+ export function getReferencedSkills(execution, skillLookup, visited = new Set()) {
57
+ const referenced = new Set();
58
+ for (const line of execution) {
59
+ const skillName = parseSkillReference(line);
60
+ if (!skillName || visited.has(skillName)) {
61
+ continue;
62
+ }
63
+ referenced.add(skillName);
64
+ const skill = skillLookup(skillName);
65
+ if (skill && skill.execution) {
66
+ const newVisited = new Set(visited);
67
+ newVisited.add(skillName);
68
+ const nested = getReferencedSkills(skill.execution, skillLookup, newVisited);
69
+ for (const name of nested) {
70
+ referenced.add(name);
71
+ }
72
+ }
73
+ }
74
+ return referenced;
75
+ }
76
+ /**
77
+ * Validate skill references don't form cycles
78
+ * Returns true if valid, false if circular reference detected
79
+ */
80
+ export function validateNoCycles(execution, skillLookup, visited = new Set()) {
81
+ try {
82
+ expandSkillReferences(execution, skillLookup, visited);
83
+ return true;
84
+ }
85
+ catch (error) {
86
+ if (error instanceof Error && error.message.includes('Circular')) {
87
+ return false;
88
+ }
89
+ throw error;
90
+ }
91
+ }
@@ -0,0 +1,169 @@
1
+ import YAML from 'yaml';
2
+ /**
3
+ * Parse a skill markdown file into structured definition
4
+ */
5
+ export function parseSkillMarkdown(content) {
6
+ const sections = extractSections(content);
7
+ // Name is required
8
+ if (!sections.name) {
9
+ return null;
10
+ }
11
+ // Description is required
12
+ if (!sections.description) {
13
+ return null;
14
+ }
15
+ // Steps are required
16
+ if (!sections.steps || sections.steps.length === 0) {
17
+ return null;
18
+ }
19
+ // Validate execution and steps have same count (if execution exists)
20
+ if (sections.execution &&
21
+ sections.execution.length !== sections.steps.length) {
22
+ return null;
23
+ }
24
+ const skill = {
25
+ name: sections.name,
26
+ description: sections.description,
27
+ steps: sections.steps,
28
+ };
29
+ if (sections.aliases && sections.aliases.length > 0) {
30
+ skill.aliases = sections.aliases;
31
+ }
32
+ if (sections.config) {
33
+ skill.config = sections.config;
34
+ }
35
+ if (sections.execution && sections.execution.length > 0) {
36
+ skill.execution = sections.execution;
37
+ }
38
+ return skill;
39
+ }
40
+ /**
41
+ * Extract sections from markdown content
42
+ */
43
+ function extractSections(content) {
44
+ const lines = content.split('\n');
45
+ const sections = {};
46
+ let currentSection = null;
47
+ let sectionLines = [];
48
+ for (const line of lines) {
49
+ // Check for section headers (### SectionName)
50
+ const headerMatch = line.match(/^###\s+(.+)$/);
51
+ if (headerMatch) {
52
+ // Process previous section
53
+ if (currentSection) {
54
+ processSectionContent(currentSection, sectionLines, sections);
55
+ }
56
+ // Start new section
57
+ currentSection = headerMatch[1].trim().toLowerCase();
58
+ sectionLines = [];
59
+ }
60
+ else if (currentSection) {
61
+ // Accumulate lines for current section
62
+ sectionLines.push(line);
63
+ }
64
+ }
65
+ // Process final section
66
+ if (currentSection) {
67
+ processSectionContent(currentSection, sectionLines, sections);
68
+ }
69
+ return sections;
70
+ }
71
+ /**
72
+ * Process accumulated section content
73
+ */
74
+ function processSectionContent(sectionName, lines, sections) {
75
+ const content = lines.join('\n').trim();
76
+ if (!content) {
77
+ return;
78
+ }
79
+ switch (sectionName) {
80
+ case 'name':
81
+ sections.name = content;
82
+ break;
83
+ case 'description':
84
+ sections.description = content;
85
+ break;
86
+ case 'aliases':
87
+ sections.aliases = extractBulletList(content);
88
+ break;
89
+ case 'config':
90
+ sections.config = parseConfigSchema(content);
91
+ break;
92
+ case 'steps':
93
+ sections.steps = extractBulletList(content);
94
+ break;
95
+ case 'execution':
96
+ sections.execution = extractBulletList(content);
97
+ break;
98
+ }
99
+ }
100
+ /**
101
+ * Extract bullet list items from content
102
+ */
103
+ function extractBulletList(content) {
104
+ const lines = content.split('\n');
105
+ const items = [];
106
+ for (const line of lines) {
107
+ const trimmed = line.trim();
108
+ // Match bullet points: "- text" or "* text"
109
+ const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
110
+ if (bulletMatch) {
111
+ items.push(bulletMatch[1].trim());
112
+ }
113
+ }
114
+ return items;
115
+ }
116
+ /**
117
+ * Parse YAML config schema
118
+ */
119
+ function parseConfigSchema(content) {
120
+ try {
121
+ const parsed = YAML.parse(content);
122
+ if (!parsed || typeof parsed !== 'object') {
123
+ return undefined;
124
+ }
125
+ return parsed;
126
+ }
127
+ catch {
128
+ return undefined;
129
+ }
130
+ }
131
+ /**
132
+ * Generate all config paths from schema
133
+ */
134
+ export function generateConfigPaths(schema, prefix = '') {
135
+ const paths = [];
136
+ for (const [key, value] of Object.entries(schema)) {
137
+ const fullKey = prefix ? `${prefix}.${key}` : key;
138
+ if (typeof value === 'string') {
139
+ // Leaf node with type annotation
140
+ paths.push(fullKey);
141
+ }
142
+ else if (typeof value === 'object') {
143
+ // Nested object - recurse
144
+ paths.push(...generateConfigPaths(value, fullKey));
145
+ }
146
+ }
147
+ return paths;
148
+ }
149
+ /**
150
+ * Get config type for a specific path
151
+ */
152
+ export function getConfigType(schema, path) {
153
+ const parts = path.split('.');
154
+ let current = schema;
155
+ for (const part of parts) {
156
+ if (typeof current === 'string') {
157
+ return undefined;
158
+ }
159
+ if (typeof current !== 'object') {
160
+ return undefined;
161
+ }
162
+ current = current[part];
163
+ }
164
+ if (typeof current === 'string' &&
165
+ (current === 'string' || current === 'boolean' || current === 'number')) {
166
+ return current;
167
+ }
168
+ return undefined;
169
+ }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'fs';
2
2
  import { homedir } from 'os';
3
3
  import { join } from 'path';
4
+ import { parseSkillMarkdown } from './skill-parser.js';
4
5
  /**
5
6
  * Get the path to the skills directory
6
7
  */
@@ -32,6 +33,31 @@ export function loadSkills() {
32
33
  return [];
33
34
  }
34
35
  }
36
+ /**
37
+ * Load and parse all skill definitions
38
+ * Returns structured skill definitions
39
+ */
40
+ export function loadSkillDefinitions() {
41
+ const skillContents = loadSkills();
42
+ const definitions = [];
43
+ for (const content of skillContents) {
44
+ const parsed = parseSkillMarkdown(content);
45
+ if (parsed) {
46
+ definitions.push(parsed);
47
+ }
48
+ }
49
+ return definitions;
50
+ }
51
+ /**
52
+ * Create skill lookup function from definitions
53
+ */
54
+ export function createSkillLookup(definitions) {
55
+ const map = new Map();
56
+ for (const definition of definitions) {
57
+ map.set(definition.name, definition);
58
+ }
59
+ return (name) => map.get(name) || null;
60
+ }
35
61
  /**
36
62
  * Format skills for inclusion in the planning prompt
37
63
  */
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Timing utilities for UI components
3
+ */
4
+ /**
5
+ * Waits for at least the minimum processing time.
6
+ * Ensures async operations don't complete too quickly for good UX.
7
+ *
8
+ * @param startTime - The timestamp when the operation started
9
+ * @param minimumTime - The minimum total time the operation should take
10
+ */
11
+ export async function ensureMinimumTime(startTime, minimumTime) {
12
+ const elapsed = Date.now() - startTime;
13
+ const remainingTime = Math.max(0, minimumTime - elapsed);
14
+ if (remainingTime > 0) {
15
+ await new Promise((resolve) => setTimeout(resolve, remainingTime));
16
+ }
17
+ }
18
+ /**
19
+ * Wraps an async operation with minimum processing time UX polish.
20
+ * Ensures successful operations take at least `minimumTime` milliseconds.
21
+ * Errors are thrown immediately without delay for better UX.
22
+ *
23
+ * @param operation - The async operation to perform
24
+ * @param minimumTime - Minimum time in milliseconds for UX polish on success
25
+ * @returns The result of the operation
26
+ */
27
+ export async function withMinimumTime(operation, minimumTime) {
28
+ const startTime = Date.now();
29
+ try {
30
+ const result = await operation();
31
+ await ensureMinimumTime(startTime, minimumTime);
32
+ return result;
33
+ }
34
+ catch (error) {
35
+ // Don't wait on error - fail fast for better UX
36
+ throw error;
37
+ }
38
+ }
@@ -38,6 +38,7 @@ import { configTool } from '../tools/config.tool.js';
38
38
  import { executeTool } from '../tools/execute.tool.js';
39
39
  import { introspectTool } from '../tools/introspect.tool.js';
40
40
  import { planTool } from '../tools/plan.tool.js';
41
+ import { validateTool } from '../tools/validate.tool.js';
41
42
  toolRegistry.register('plan', {
42
43
  schema: planTool,
43
44
  instructionsPath: 'config/PLAN.md',
@@ -58,3 +59,7 @@ toolRegistry.register('execute', {
58
59
  schema: executeTool,
59
60
  instructionsPath: 'config/EXECUTE.md',
60
61
  });
62
+ toolRegistry.register('validate', {
63
+ schema: validateTool,
64
+ instructionsPath: 'config/VALIDATE.md',
65
+ });
@@ -0,0 +1,43 @@
1
+ export const validateTool = {
2
+ name: 'validate',
3
+ description: 'Validate skill requirements and generate natural language descriptions for missing configuration values. Given skill context and missing config paths, create CONFIG tasks with helpful, contextual descriptions.',
4
+ input_schema: {
5
+ type: 'object',
6
+ properties: {
7
+ message: {
8
+ type: 'string',
9
+ description: 'Empty string or brief message (not shown to user, can be left empty)',
10
+ },
11
+ tasks: {
12
+ type: 'array',
13
+ description: 'Array of CONFIG tasks with natural language descriptions for missing config values',
14
+ items: {
15
+ type: 'object',
16
+ properties: {
17
+ action: {
18
+ type: 'string',
19
+ description: 'Natural language description explaining what the config value is for, followed by the config path in curly brackets {config.path}. Example: "Path to Alpha project repository (legacy implementation) {project.alpha.repo}"',
20
+ },
21
+ type: {
22
+ type: 'string',
23
+ description: 'Must be "config" for all tasks returned by this tool',
24
+ },
25
+ params: {
26
+ type: 'object',
27
+ description: 'Must include key field with the config path',
28
+ properties: {
29
+ key: {
30
+ type: 'string',
31
+ description: 'The config path (e.g., "opera.gx.repo")',
32
+ },
33
+ },
34
+ required: ['key'],
35
+ },
36
+ },
37
+ required: ['action', 'type', 'params'],
38
+ },
39
+ },
40
+ },
41
+ required: ['message', 'tasks'],
42
+ },
43
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Type definitions for the structured skill system
3
+ */
4
+ export {};
@@ -13,6 +13,7 @@ export var ComponentName;
13
13
  ComponentName["Answer"] = "answer";
14
14
  ComponentName["AnswerDisplay"] = "answerDisplay";
15
15
  ComponentName["Execute"] = "execute";
16
+ ComponentName["Validate"] = "validate";
16
17
  })(ComponentName || (ComponentName = {}));
17
18
  export var TaskType;
18
19
  (function (TaskType) {
package/dist/ui/Answer.js CHANGED
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
4
4
  import { Colors, getTextColor } from '../services/colors.js';
5
5
  import { useInput } from '../services/keyboard.js';
6
6
  import { formatErrorMessage } from '../services/messages.js';
7
+ import { withMinimumTime } from '../services/timing.js';
7
8
  import { Spinner } from './Spinner.js';
8
9
  const MINIMUM_PROCESSING_TIME = 400;
9
10
  export function Answer({ question, state, service, onError, onComplete, onAborted, }) {
@@ -30,13 +31,9 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
30
31
  }
31
32
  let mounted = true;
32
33
  async function process(svc) {
33
- const startTime = Date.now();
34
34
  try {
35
- // Call answer tool
36
- const result = await svc.processWithTool(question, 'answer');
37
- const elapsed = Date.now() - startTime;
38
- const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
39
- await new Promise((resolve) => setTimeout(resolve, remainingTime));
35
+ // Call answer tool with minimum processing time for UX polish
36
+ const result = await withMinimumTime(() => svc.processWithTool(question, 'answer'), MINIMUM_PROCESSING_TIME);
40
37
  if (mounted) {
41
38
  // Extract answer from result
42
39
  const answer = result.answer || '';
@@ -45,9 +42,6 @@ export function Answer({ question, state, service, onError, onComplete, onAborte
45
42
  }
46
43
  }
47
44
  catch (err) {
48
- const elapsed = Date.now() - startTime;
49
- const remainingTime = Math.max(0, MINIMUM_PROCESSING_TIME - elapsed);
50
- await new Promise((resolve) => setTimeout(resolve, remainingTime));
51
45
  if (mounted) {
52
46
  const errorMessage = formatErrorMessage(err);
53
47
  setIsLoading(false);
@@ -5,6 +5,7 @@ import { TaskType } from '../types/types.js';
5
5
  import { Colors } from '../services/colors.js';
6
6
  import { useInput } from '../services/keyboard.js';
7
7
  import { formatErrorMessage } from '../services/messages.js';
8
+ import { ensureMinimumTime } from '../services/timing.js';
8
9
  import { Spinner } from './Spinner.js';
9
10
  const MIN_PROCESSING_TIME = 1000; // purely for visual effect
10
11
  export function Command({ command, state, service, children, onError, onComplete, onAborted, }) {
@@ -42,18 +43,14 @@ export function Command({ command, state, service, children, onError, onComplete
42
43
  // Call CONFIG tool to get specific config keys
43
44
  result = await svc.processWithTool(query, 'config');
44
45
  }
45
- const elapsed = Date.now() - startTime;
46
- const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
47
- await new Promise((resolve) => setTimeout(resolve, remainingTime));
46
+ await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
48
47
  if (mounted) {
49
48
  setIsLoading(false);
50
49
  onComplete?.(result.message, result.tasks);
51
50
  }
52
51
  }
53
52
  catch (err) {
54
- const elapsed = Date.now() - startTime;
55
- const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
56
- await new Promise((resolve) => setTimeout(resolve, remainingTime));
53
+ await ensureMinimumTime(startTime, MIN_PROCESSING_TIME);
57
54
  if (mounted) {
58
55
  const errorMessage = formatErrorMessage(err);
59
56
  setIsLoading(false);
@@ -13,6 +13,7 @@ import { Message } from './Message.js';
13
13
  import { Plan } from './Plan.js';
14
14
  import { Refinement } from './Refinement.js';
15
15
  import { Report } from './Report.js';
16
+ import { Validate } from './Validate.js';
16
17
  import { Welcome } from './Welcome.js';
17
18
  export const Component = React.memo(function Component({ def, debug, }) {
18
19
  switch (def.name) {
@@ -21,7 +22,7 @@ export const Component = React.memo(function Component({ def, debug, }) {
21
22
  case ComponentName.Config: {
22
23
  const props = def.props;
23
24
  const state = def.state;
24
- return _jsx(Config, { ...props, state: state });
25
+ return _jsx(Config, { ...props, state: state, debug: debug });
25
26
  }
26
27
  case ComponentName.Command: {
27
28
  const props = def.props;
@@ -66,5 +67,10 @@ export const Component = React.memo(function Component({ def, debug, }) {
66
67
  const state = def.state;
67
68
  return _jsx(Execute, { ...props, state: state });
68
69
  }
70
+ case ComponentName.Validate: {
71
+ const props = def.props;
72
+ const state = def.state;
73
+ return _jsx(Validate, { ...props, state: state });
74
+ }
69
75
  }
70
76
  });
package/dist/ui/Config.js CHANGED
@@ -57,7 +57,7 @@ function SelectionStep({ options, selectedIndex, isCurrentStep, }) {
57
57
  return (_jsx(Box, { marginRight: 2, children: _jsx(Text, { dimColor: !isSelected || !isCurrentStep, bold: isSelected, children: option.label }) }, option.value));
58
58
  }) }));
59
59
  }
60
- export function Config({ steps, state, onFinished, onAborted }) {
60
+ export function Config({ steps, state, debug, onFinished, onAborted }) {
61
61
  const done = state?.done ?? false;
62
62
  const [step, setStep] = React.useState(done ? steps.length : 0);
63
63
  const [values, setValues] = React.useState(() => {
@@ -220,6 +220,6 @@ export function Config({ steps, state, onFinished, onAborted }) {
220
220
  if (!shouldShow) {
221
221
  return null;
222
222
  }
223
- return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
223
+ return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsxs(Box, { children: [_jsx(Text, { children: stepConfig.description }), _jsx(Text, { children: ": " }), debug && stepConfig.path && (_jsxs(Text, { color: Colors.Type.Define, children: ['{', stepConfig.path, '}'] }))] }), _jsxs(Box, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: Colors.Action.Select, dimColor: !isCurrentStep, children: ">" }), _jsx(Text, { children: " " }), renderStepInput(stepConfig, isCurrentStep)] })] }, stepConfig.key));
224
224
  }) }));
225
225
  }