prompt-language-shell 0.2.0 → 0.2.4
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.
- package/dist/config/PLAN.md +6 -1
- package/dist/index.js +3 -3
- package/dist/services/anthropic.js +1 -1
- package/dist/services/skills.js +7 -2
- package/dist/services/skills.test.js +115 -0
- package/dist/services/tool-registry.js +1 -2
- package/dist/types/components.js +1 -0
- package/dist/ui/Column.js +6 -0
- package/dist/ui/Command.js +67 -20
- package/dist/ui/Component.js +20 -0
- package/dist/ui/Config.js +44 -0
- package/dist/ui/List.js +7 -0
- package/dist/ui/Main.js +20 -9
- package/dist/ui/Panel.js +5 -0
- package/dist/ui/Separator.js +6 -0
- package/dist/ui/Welcome.js +18 -9
- package/dist/ui/renderComponent.js +3 -3
- package/package.json +14 -9
- package/dist/config/SYSTEM.md +0 -156
- package/dist/services/claude.js +0 -54
- package/dist/ui/CommandProcessor.js +0 -31
package/dist/config/PLAN.md
CHANGED
|
@@ -84,6 +84,10 @@ derived from available skills:
|
|
|
84
84
|
parameters
|
|
85
85
|
3. Present these as concrete options, NOT generic categories
|
|
86
86
|
4. Each option should be something the user can directly select and execute
|
|
87
|
+
5. Format options WITHOUT brackets. Use commas to separate extra information
|
|
88
|
+
instead. For example:
|
|
89
|
+
- CORRECT: "Build project Alpha, the legacy version"
|
|
90
|
+
- WRONG: "Build project Alpha (the legacy version)"
|
|
87
91
|
|
|
88
92
|
Example:
|
|
89
93
|
- Available skills: "Build Product" (variant A, variant B), "Deploy
|
|
@@ -440,7 +444,8 @@ Examples showing proper use of skills and disambiguation:
|
|
|
440
444
|
- "build" with build skill requiring {PROJECT} parameter (Alpha, Beta, Gamma,
|
|
441
445
|
Delta) → One task: type "define", action "Clarify which project to build",
|
|
442
446
|
params { options: ["Build Alpha", "Build Beta", "Build Gamma", "Build
|
|
443
|
-
Delta"] }
|
|
447
|
+
Delta"] }. NOTE: If variants have descriptions, format as "Build Alpha, the
|
|
448
|
+
legacy version" NOT "Build Alpha (the legacy version)"
|
|
444
449
|
- "build Alpha" with same build skill → Three tasks extracted from skill
|
|
445
450
|
steps: "Navigate to the Alpha project's root directory", "Execute the Alpha
|
|
446
451
|
project generation script", "Compile the Alpha source code"
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
5
4
|
import { dirname, join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
6
|
import { render, Text } from 'ink';
|
|
7
|
-
import {
|
|
7
|
+
import { ConfigError, configExists, loadConfig, saveConfig, } from './services/config.js';
|
|
8
8
|
import { createAnthropicService } from './services/anthropic.js';
|
|
9
9
|
import { Main } from './ui/Main.js';
|
|
10
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
-
import {
|
|
2
|
+
import { formatSkillsForPrompt, loadSkills } from './skills.js';
|
|
3
3
|
import { toolRegistry } from './tool-registry.js';
|
|
4
4
|
export class AnthropicService {
|
|
5
5
|
client;
|
package/dist/services/skills.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'fs';
|
|
3
2
|
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
4
|
/**
|
|
5
5
|
* Get the path to the skills directory
|
|
6
6
|
*/
|
|
@@ -46,6 +46,11 @@ export function formatSkillsForPrompt(skills) {
|
|
|
46
46
|
The following skills define domain-specific workflows. When the user's
|
|
47
47
|
query matches a skill, incorporate the skill's steps into your plan.
|
|
48
48
|
|
|
49
|
+
**IMPORTANT**: When creating options from skill descriptions, do NOT use
|
|
50
|
+
brackets for additional information. Use commas instead. For example:
|
|
51
|
+
- CORRECT: "Build project Alpha, the legacy version"
|
|
52
|
+
- WRONG: "Build project Alpha (the legacy version)"
|
|
53
|
+
|
|
49
54
|
`;
|
|
50
55
|
const skillsContent = skills.join('\n\n');
|
|
51
56
|
return header + skillsContent;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdirSync, writeFileSync, rmSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { getSkillsDirectory, loadSkills, formatSkillsForPrompt, } from './skills.js';
|
|
6
|
+
describe('skills service', () => {
|
|
7
|
+
let originalHome;
|
|
8
|
+
let tempHome;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Mock HOME to point to temp directory
|
|
11
|
+
originalHome = process.env.HOME;
|
|
12
|
+
tempHome = join(tmpdir(), `pls-home-test-${Date.now()}`);
|
|
13
|
+
mkdirSync(tempHome, { recursive: true });
|
|
14
|
+
process.env.HOME = tempHome;
|
|
15
|
+
// Create .pls/skills directory structure
|
|
16
|
+
const plsDir = join(tempHome, '.pls');
|
|
17
|
+
mkdirSync(plsDir, { recursive: true });
|
|
18
|
+
mkdirSync(join(plsDir, 'skills'), { recursive: true });
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
// Restore original HOME first
|
|
22
|
+
if (originalHome !== undefined) {
|
|
23
|
+
process.env.HOME = originalHome;
|
|
24
|
+
}
|
|
25
|
+
// Clean up temp directory
|
|
26
|
+
if (existsSync(tempHome)) {
|
|
27
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
describe('getSkillsDirectory', () => {
|
|
31
|
+
it('returns path to .pls/skills in home directory', () => {
|
|
32
|
+
const skillsDir = getSkillsDirectory();
|
|
33
|
+
expect(skillsDir).toContain('.pls');
|
|
34
|
+
expect(skillsDir).toContain('skills');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('loadSkills', () => {
|
|
38
|
+
it('returns empty array when skills directory does not exist', () => {
|
|
39
|
+
// Remove the skills directory
|
|
40
|
+
const skillsDir = getSkillsDirectory();
|
|
41
|
+
if (existsSync(skillsDir)) {
|
|
42
|
+
rmSync(skillsDir, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
const skills = loadSkills();
|
|
45
|
+
expect(skills).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
it('returns empty array when skills directory is empty', () => {
|
|
48
|
+
const skills = loadSkills();
|
|
49
|
+
expect(skills).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it('loads single skill file', () => {
|
|
52
|
+
const skillsDir = getSkillsDirectory();
|
|
53
|
+
const skillContent = `### Name
|
|
54
|
+
Build Opera
|
|
55
|
+
|
|
56
|
+
### Description
|
|
57
|
+
Run Opera Desktop browser build
|
|
58
|
+
|
|
59
|
+
### Steps
|
|
60
|
+
Navigate to the project directory, run the project generation script, run the compilation`;
|
|
61
|
+
writeFileSync(join(skillsDir, 'opera.md'), skillContent, 'utf-8');
|
|
62
|
+
const skills = loadSkills();
|
|
63
|
+
expect(skills).toHaveLength(1);
|
|
64
|
+
expect(skills[0]).toBe(skillContent);
|
|
65
|
+
});
|
|
66
|
+
it('loads multiple skill files', () => {
|
|
67
|
+
const skillsDir = getSkillsDirectory();
|
|
68
|
+
const skill1 = 'Skill 1 content';
|
|
69
|
+
const skill2 = 'Skill 2 content';
|
|
70
|
+
writeFileSync(join(skillsDir, 'skill1.md'), skill1, 'utf-8');
|
|
71
|
+
writeFileSync(join(skillsDir, 'skill2.md'), skill2, 'utf-8');
|
|
72
|
+
const skills = loadSkills();
|
|
73
|
+
expect(skills).toHaveLength(2);
|
|
74
|
+
expect(skills).toContain(skill1);
|
|
75
|
+
expect(skills).toContain(skill2);
|
|
76
|
+
});
|
|
77
|
+
it('ignores non-markdown files', () => {
|
|
78
|
+
const skillsDir = getSkillsDirectory();
|
|
79
|
+
writeFileSync(join(skillsDir, 'skill.md'), 'Skill content', 'utf-8');
|
|
80
|
+
writeFileSync(join(skillsDir, 'readme.txt'), 'Not a skill', 'utf-8');
|
|
81
|
+
writeFileSync(join(skillsDir, 'data.json'), '{}', 'utf-8');
|
|
82
|
+
const skills = loadSkills();
|
|
83
|
+
expect(skills).toHaveLength(1);
|
|
84
|
+
expect(skills[0]).toBe('Skill content');
|
|
85
|
+
});
|
|
86
|
+
it('handles both .md and .MD extensions', () => {
|
|
87
|
+
const skillsDir = getSkillsDirectory();
|
|
88
|
+
writeFileSync(join(skillsDir, 'skill1.md'), 'Lowercase', 'utf-8');
|
|
89
|
+
writeFileSync(join(skillsDir, 'skill2.MD'), 'Uppercase', 'utf-8');
|
|
90
|
+
const skills = loadSkills();
|
|
91
|
+
expect(skills).toHaveLength(2);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('formatSkillsForPrompt', () => {
|
|
95
|
+
it('returns empty string when no skills', () => {
|
|
96
|
+
const formatted = formatSkillsForPrompt([]);
|
|
97
|
+
expect(formatted).toBe('');
|
|
98
|
+
});
|
|
99
|
+
it('formats single skill with header', () => {
|
|
100
|
+
const skills = ['Skill 1 content'];
|
|
101
|
+
const formatted = formatSkillsForPrompt(skills);
|
|
102
|
+
expect(formatted).toContain('## Available Skills');
|
|
103
|
+
expect(formatted).toContain('The following skills define domain-specific workflows');
|
|
104
|
+
expect(formatted).toContain('Skill 1 content');
|
|
105
|
+
});
|
|
106
|
+
it('formats multiple skills separated by blank lines', () => {
|
|
107
|
+
const skills = ['Skill 1', 'Skill 2', 'Skill 3'];
|
|
108
|
+
const formatted = formatSkillsForPrompt(skills);
|
|
109
|
+
expect(formatted).toContain('Skill 1');
|
|
110
|
+
expect(formatted).toContain('Skill 2');
|
|
111
|
+
expect(formatted).toContain('Skill 3');
|
|
112
|
+
expect(formatted).toContain('\n\n');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
2
|
+
import { dirname, resolve } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { dirname } from 'path';
|
|
5
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
5
|
const __dirname = dirname(__filename);
|
|
7
6
|
class ToolRegistry {
|
package/dist/types/components.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { Component } from './Component.js';
|
|
4
|
+
export const Column = ({ items }) => {
|
|
5
|
+
return (_jsx(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: _jsx(Component, { def: item }) }, index))) }));
|
|
6
|
+
};
|
package/dist/ui/Command.js
CHANGED
|
@@ -2,27 +2,77 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { TaskType } from '../types/components.js';
|
|
5
|
+
import { List } from './List.js';
|
|
5
6
|
import { Spinner } from './Spinner.js';
|
|
6
7
|
const MIN_PROCESSING_TIME = 1000; // purely for visual effect
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
// Color palette
|
|
9
|
+
const ColorPalette = {
|
|
10
|
+
[TaskType.Config]: {
|
|
11
|
+
description: '#ffffff', // white
|
|
12
|
+
type: '#5c9ccc', // cyan
|
|
13
|
+
},
|
|
14
|
+
[TaskType.Plan]: {
|
|
15
|
+
description: '#ffffff', // white
|
|
16
|
+
type: '#cc5c9c', // magenta
|
|
17
|
+
},
|
|
18
|
+
[TaskType.Execute]: {
|
|
19
|
+
description: '#ffffff', // white
|
|
20
|
+
type: '#4a9a7a', // green
|
|
21
|
+
},
|
|
22
|
+
[TaskType.Answer]: {
|
|
23
|
+
description: '#ffffff', // white
|
|
24
|
+
type: '#9c5ccc', // purple
|
|
25
|
+
},
|
|
26
|
+
[TaskType.Report]: {
|
|
27
|
+
description: '#ffffff', // white
|
|
28
|
+
type: '#cc9c5c', // orange
|
|
29
|
+
},
|
|
30
|
+
[TaskType.Define]: {
|
|
31
|
+
description: '#ffffff', // white
|
|
32
|
+
type: '#cc9c5c', // amber
|
|
33
|
+
},
|
|
34
|
+
[TaskType.Ignore]: {
|
|
35
|
+
description: '#cccc5c', // yellow
|
|
36
|
+
type: '#cc7a5c', // orange
|
|
37
|
+
},
|
|
38
|
+
[TaskType.Select]: {
|
|
39
|
+
description: '#888888', // grey
|
|
40
|
+
type: '#5c8cbc', // steel blue
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
function taskToListItem(task) {
|
|
44
|
+
const colors = ColorPalette[task.type];
|
|
45
|
+
const item = {
|
|
46
|
+
description: {
|
|
47
|
+
text: task.action,
|
|
48
|
+
color: colors.description,
|
|
49
|
+
},
|
|
50
|
+
type: {
|
|
51
|
+
text: task.type,
|
|
52
|
+
color: colors.type,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
// Add children for Define tasks with options
|
|
56
|
+
if (task.type === TaskType.Define && Array.isArray(task.params?.options)) {
|
|
57
|
+
const selectColors = ColorPalette[TaskType.Select];
|
|
58
|
+
item.children = task.params.options.map((option) => ({
|
|
59
|
+
description: {
|
|
60
|
+
text: String(option),
|
|
61
|
+
color: selectColors.description,
|
|
62
|
+
},
|
|
63
|
+
type: {
|
|
64
|
+
text: TaskType.Select,
|
|
65
|
+
color: selectColors.type,
|
|
66
|
+
},
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return item;
|
|
9
70
|
}
|
|
10
|
-
function
|
|
11
|
-
if (taskType === TaskType.Ignore)
|
|
12
|
-
return 'red';
|
|
13
|
-
if (taskType === TaskType.Define)
|
|
14
|
-
return 'blue';
|
|
15
|
-
return 'greenBright';
|
|
16
|
-
}
|
|
17
|
-
function shouldDimTaskType(taskType) {
|
|
18
|
-
return taskType !== TaskType.Define;
|
|
19
|
-
}
|
|
20
|
-
export function Command({ command, state, service, tasks, error: errorProp, systemPrompt: systemPromptProp, }) {
|
|
71
|
+
export function Command({ command, state, service, error: errorProp, children, }) {
|
|
21
72
|
const done = state?.done ?? false;
|
|
22
|
-
const [processedTasks, setProcessedTasks] = useState(tasks || []);
|
|
23
|
-
const [systemPrompt, setSystemPrompt] = useState(systemPromptProp);
|
|
24
73
|
const [error, setError] = useState(state?.error || errorProp || null);
|
|
25
74
|
const [isLoading, setIsLoading] = useState(state?.isLoading ?? !done);
|
|
75
|
+
const [tasks, setTasks] = useState([]);
|
|
26
76
|
useEffect(() => {
|
|
27
77
|
// Skip processing if done (showing historical/final state)
|
|
28
78
|
if (done) {
|
|
@@ -43,8 +93,7 @@ export function Command({ command, state, service, tasks, error: errorProp, syst
|
|
|
43
93
|
const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
|
|
44
94
|
await new Promise((resolve) => setTimeout(resolve, remainingTime));
|
|
45
95
|
if (mounted) {
|
|
46
|
-
|
|
47
|
-
setSystemPrompt(result.systemPrompt);
|
|
96
|
+
setTasks(result.tasks);
|
|
48
97
|
setIsLoading(false);
|
|
49
98
|
}
|
|
50
99
|
}
|
|
@@ -63,7 +112,5 @@ export function Command({ command, state, service, tasks, error: errorProp, syst
|
|
|
63
112
|
mounted = false;
|
|
64
113
|
};
|
|
65
114
|
}, [command, done, service]);
|
|
66
|
-
return (_jsxs(Box, { alignSelf: "flex-start", marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", 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] }) })),
|
|
67
|
-
task.params?.options &&
|
|
68
|
-
Array.isArray(task.params.options) && (_jsx(Box, { flexDirection: "column", marginLeft: 4, children: task.params.options.map((option, optIndex) => (_jsx(Box, { children: _jsxs(Text, { color: "whiteBright", dimColor: true, children: ["- ", String(option)] }) }, optIndex))) })))] }, index))) }))] }));
|
|
115
|
+
return (_jsxs(Box, { alignSelf: "flex-start", marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: "gray", 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] }) })), !isLoading && tasks.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsx(List, { items: tasks.map(taskToListItem) }) })), children] }));
|
|
69
116
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Command } from './Command.js';
|
|
3
|
+
import { Config } from './Config.js';
|
|
4
|
+
import { Welcome } from './Welcome.js';
|
|
5
|
+
export function Component({ def }) {
|
|
6
|
+
switch (def.name) {
|
|
7
|
+
case 'welcome':
|
|
8
|
+
return _jsx(Welcome, { ...def.props });
|
|
9
|
+
case 'config': {
|
|
10
|
+
const props = def.props;
|
|
11
|
+
const state = def.state;
|
|
12
|
+
return _jsx(Config, { ...props, state: state });
|
|
13
|
+
}
|
|
14
|
+
case 'command': {
|
|
15
|
+
const props = def.props;
|
|
16
|
+
const state = def.state;
|
|
17
|
+
return _jsx(Command, { ...props, state: state });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
export function Config({ steps, state, onFinished }) {
|
|
6
|
+
const done = state?.done ?? false;
|
|
7
|
+
const [step, setStep] = React.useState(done ? steps.length : 0);
|
|
8
|
+
const [values, setValues] = React.useState(() => {
|
|
9
|
+
const initial = {};
|
|
10
|
+
steps.forEach((step) => {
|
|
11
|
+
if (step.value !== null) {
|
|
12
|
+
initial[step.key] = step.value;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return initial;
|
|
16
|
+
});
|
|
17
|
+
const [inputValue, setInputValue] = React.useState('');
|
|
18
|
+
const handleSubmit = (value) => {
|
|
19
|
+
const currentStepConfig = steps[step];
|
|
20
|
+
const finalValue = value.trim() || currentStepConfig.value || '';
|
|
21
|
+
const newValues = { ...values, [currentStepConfig.key]: finalValue };
|
|
22
|
+
setValues(newValues);
|
|
23
|
+
setInputValue('');
|
|
24
|
+
if (step === steps.length - 1) {
|
|
25
|
+
// Last step completed
|
|
26
|
+
if (onFinished) {
|
|
27
|
+
onFinished(newValues);
|
|
28
|
+
}
|
|
29
|
+
setStep(steps.length);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
setStep(step + 1);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [steps.map((stepConfig, index) => {
|
|
36
|
+
const isCurrentStep = index === step && !done;
|
|
37
|
+
const isCompleted = index < step || done;
|
|
38
|
+
const shouldShow = isCompleted || isCurrentStep;
|
|
39
|
+
if (!shouldShow) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [_jsx(Box, { children: _jsxs(Text, { children: [stepConfig.description, ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), isCurrentStep ? (_jsx(TextInput, { value: inputValue, onChange: setInputValue, onSubmit: handleSubmit })) : (_jsx(Text, { dimColor: true, children: values[stepConfig.key] || '' }))] })] }, stepConfig.key));
|
|
43
|
+
}), step === steps.length && !done && (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: "green", children: "\u2713 Configuration complete" }) }))] }));
|
|
44
|
+
}
|
package/dist/ui/List.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Separator } from './Separator.js';
|
|
4
|
+
export const List = ({ items, level = 0 }) => {
|
|
5
|
+
const marginLeft = level > 0 ? 4 : 0;
|
|
6
|
+
return (_jsx(Box, { flexDirection: "column", marginLeft: marginLeft, children: items.map((item, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "whiteBright", children: ' - ' }), _jsx(Text, { color: item.description.color, children: item.description.text }), _jsx(Separator, {}), _jsx(Text, { color: item.type.color, children: item.type.text })] }), item.children && item.children.length > 0 && (_jsx(List, { items: item.children, level: level + 1 }))] }, index))) }));
|
|
7
|
+
};
|
package/dist/ui/Main.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { History } from './History.js';
|
|
5
|
-
import { renderComponent } from './renderComponent.js';
|
|
3
|
+
import { Column } from './Column.js';
|
|
6
4
|
export const Main = ({ app, command, service, isReady, onConfigured, }) => {
|
|
7
5
|
const [history, setHistory] = React.useState([]);
|
|
8
6
|
const [current, setCurrent] = React.useState(null);
|
|
9
7
|
React.useEffect(() => {
|
|
10
8
|
// Initialize history and current component based on props
|
|
11
9
|
if (!isReady) {
|
|
12
|
-
// Not configured - show welcome in history,
|
|
10
|
+
// Not configured - show welcome in history, config as current
|
|
13
11
|
setHistory([
|
|
14
12
|
{
|
|
15
13
|
name: 'welcome',
|
|
@@ -18,14 +16,26 @@ export const Main = ({ app, command, service, isReady, onConfigured, }) => {
|
|
|
18
16
|
},
|
|
19
17
|
},
|
|
20
18
|
]);
|
|
19
|
+
const configSteps = [
|
|
20
|
+
{ description: 'Anthropic API key', key: 'key', value: null },
|
|
21
|
+
{
|
|
22
|
+
description: 'Model',
|
|
23
|
+
key: 'model',
|
|
24
|
+
value: 'claude-haiku-4-5-20251001',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
21
27
|
setCurrent({
|
|
22
|
-
name: '
|
|
28
|
+
name: 'config',
|
|
23
29
|
state: {
|
|
24
30
|
done: false,
|
|
25
|
-
step: 'key',
|
|
26
31
|
},
|
|
27
32
|
props: {
|
|
28
|
-
|
|
33
|
+
steps: configSteps,
|
|
34
|
+
onFinished: (config) => {
|
|
35
|
+
if (onConfigured) {
|
|
36
|
+
onConfigured(config);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
29
39
|
},
|
|
30
40
|
});
|
|
31
41
|
}
|
|
@@ -51,5 +61,6 @@ export const Main = ({ app, command, service, isReady, onConfigured, }) => {
|
|
|
51
61
|
});
|
|
52
62
|
}
|
|
53
63
|
}, [isReady, command, service, app, onConfigured]);
|
|
54
|
-
|
|
64
|
+
const items = [...history, ...(current ? [current] : [])];
|
|
65
|
+
return _jsx(Column, { items: items });
|
|
55
66
|
};
|
package/dist/ui/Panel.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
export const Separator = ({ color = '#666666', spaces = 1, }) => {
|
|
4
|
+
const spacing = ' '.repeat(spaces);
|
|
5
|
+
return (_jsxs(Text, { color: color, children: [spacing, "\u203A", spacing] }));
|
|
6
|
+
};
|
package/dist/ui/Welcome.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
.split('. ')
|
|
6
|
-
.map((line) => line.replace(/\.$/, ''))
|
|
7
|
-
.filter(Boolean);
|
|
8
|
-
// Transform package name: "prompt-language-shell" -> "Prompt Language Shell"
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { Panel } from './Panel.js';
|
|
4
|
+
function Header({ app }) {
|
|
9
5
|
const words = app.name
|
|
10
6
|
.split('-')
|
|
11
7
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1));
|
|
12
|
-
return (
|
|
8
|
+
return (_jsxs(Box, { marginBottom: 1, gap: 1, children: [words.map((word, index) => (_jsx(Text, { color: "greenBright", bold: true, children: word }, index))), _jsxs(Text, { color: "whiteBright", dimColor: true, children: ["v", app.version] }), app.isDev && _jsx(Text, { color: "yellowBright", children: "dev" })] }));
|
|
9
|
+
}
|
|
10
|
+
function Description({ description }) {
|
|
11
|
+
const lines = description
|
|
12
|
+
.split('. ')
|
|
13
|
+
.map((line) => line.replace(/\.$/, ''))
|
|
14
|
+
.filter(Boolean);
|
|
15
|
+
return (_jsx(_Fragment, { children: lines.map((line, index) => (_jsx(Box, { children: _jsxs(Text, { color: "white", children: [line, "."] }) }, index))) }));
|
|
16
|
+
}
|
|
17
|
+
function Usage() {
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "brightWhite", bold: true, children: "Usage:" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "whiteBright", dimColor: true, children: ">" }), _jsxs(Box, { gap: 1, children: [_jsx(Text, { color: "greenBright", bold: true, children: "pls" }), _jsx(Text, { color: "yellow", bold: true, children: "[describe your request]" })] })] })] }));
|
|
19
|
+
}
|
|
20
|
+
export function Welcome({ app }) {
|
|
21
|
+
return (_jsx(Box, { alignSelf: "flex-start", marginBottom: 1, children: _jsxs(Panel, { children: [_jsx(Header, { app: app }), _jsx(Description, { description: app.description }), _jsx(Usage, {})] }) }));
|
|
13
22
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Welcome } from './Welcome.js';
|
|
3
|
-
import {
|
|
3
|
+
import { Config } from './Config.js';
|
|
4
4
|
import { Command } from './Command.js';
|
|
5
5
|
export function renderComponent(def) {
|
|
6
6
|
switch (def.name) {
|
|
7
7
|
case 'welcome':
|
|
8
8
|
return _jsx(Welcome, { ...def.props });
|
|
9
|
-
case '
|
|
10
|
-
return (_jsx(
|
|
9
|
+
case 'config':
|
|
10
|
+
return (_jsx(Config, { ...def.props, state: 'state' in def ? def.state : undefined }));
|
|
11
11
|
case 'command':
|
|
12
12
|
return (_jsx(Command, { ...def.props, state: 'state' in def ? def.state : undefined }));
|
|
13
13
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prompt-language-shell",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Your personal command-line concierge. Ask politely, and it gets things done.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,25 +13,29 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "tsc && chmod +x dist/index.js && mkdir -p dist/config && cp src/config/*.md dist/config/",
|
|
15
15
|
"dev": "npm run build && tsc --watch",
|
|
16
|
-
"prepare": "
|
|
16
|
+
"prepare": "husky",
|
|
17
|
+
"prepublishOnly": "npm run check",
|
|
17
18
|
"test": "vitest run",
|
|
18
19
|
"test:watch": "vitest",
|
|
19
20
|
"format": "prettier --write '**/*.{ts,tsx}'",
|
|
20
21
|
"format:check": "prettier --check '**/*.{ts,tsx}'",
|
|
21
22
|
"lint": "eslint .",
|
|
22
|
-
"lint:fix": "eslint --fix ."
|
|
23
|
+
"lint:fix": "eslint --fix .",
|
|
24
|
+
"check": "npm run build && npm run test && npm run lint && npm run format:check"
|
|
23
25
|
},
|
|
24
26
|
"repository": {
|
|
25
27
|
"type": "git",
|
|
26
28
|
"url": "git+https://github.com/aswitalski/pls.git"
|
|
27
29
|
},
|
|
28
30
|
"keywords": [
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
31
|
+
"command line interface",
|
|
32
|
+
"developer tools",
|
|
33
|
+
"interactive shell",
|
|
34
|
+
"natural language processing",
|
|
35
|
+
"personal concierge",
|
|
36
|
+
"prompt language",
|
|
37
|
+
"task execution",
|
|
38
|
+
"workflow automation"
|
|
35
39
|
],
|
|
36
40
|
"author": "Sensei Aleksander Świtalski",
|
|
37
41
|
"license": "ISC",
|
|
@@ -50,6 +54,7 @@
|
|
|
50
54
|
"@types/node": "^20.10.6",
|
|
51
55
|
"@types/react": "^19.2.2",
|
|
52
56
|
"eslint": "^9.17.0",
|
|
57
|
+
"husky": "^9.1.7",
|
|
53
58
|
"prettier": "^3.6.2",
|
|
54
59
|
"typescript": "^5.3.3",
|
|
55
60
|
"typescript-eslint": "^8.19.1",
|
package/dist/config/SYSTEM.md
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
You are a command-line assistant for a CLI tool called "pls" (please) that
|
|
2
|
-
helps users perform filesystem and system operations using natural language.
|
|
3
|
-
|
|
4
|
-
Your task is to refine the user's command into clear, professional English while
|
|
5
|
-
preserving the original intent. Apply minimal necessary changes to achieve
|
|
6
|
-
optimal clarity. Focus on:
|
|
7
|
-
|
|
8
|
-
- Correcting grammar and sentence structure
|
|
9
|
-
- Replacing words with more precise or contextually appropriate alternatives,
|
|
10
|
-
even when the original word is grammatically correct
|
|
11
|
-
- Using professional, clear terminology suitable for technical documentation
|
|
12
|
-
- Maintaining natural, fluent English phrasing
|
|
13
|
-
- Preserving the original intent and meaning
|
|
14
|
-
- Being concise and unambiguous
|
|
15
|
-
|
|
16
|
-
Prioritize clarity and precision over brevity. Choose the most appropriate word
|
|
17
|
-
for the context, not just an acceptable one.
|
|
18
|
-
|
|
19
|
-
## Multiple Tasks
|
|
20
|
-
|
|
21
|
-
If the user provides multiple tasks separated by commas (,), semicolons (;), or
|
|
22
|
-
the word "and", OR if the user asks a complex question that requires multiple
|
|
23
|
-
steps to answer, you must:
|
|
24
|
-
|
|
25
|
-
1. Identify each individual task or step
|
|
26
|
-
2. Break complex questions into separate, simpler tasks
|
|
27
|
-
3. Return a JSON array of corrected tasks
|
|
28
|
-
4. Use this exact format: ["task 1", "task 2", "task 3"]
|
|
29
|
-
|
|
30
|
-
When breaking down complex questions:
|
|
31
|
-
- Split compound questions into individual queries
|
|
32
|
-
- Separate conditional checks into distinct tasks
|
|
33
|
-
- Keep each task simple and focused on one operation
|
|
34
|
-
|
|
35
|
-
**IMPORTANT: Before returning a JSON array, perform this validation:**
|
|
36
|
-
1. Check each task for semantic duplicates (same meaning, different words)
|
|
37
|
-
2. Verify each task provides unique, distinct value
|
|
38
|
-
3. If tasks overlap, merge them or keep only one
|
|
39
|
-
4. If unsure whether to split, default to a single task
|
|
40
|
-
5. Ask yourself: "Would executing these tasks result in duplicate work?"
|
|
41
|
-
|
|
42
|
-
## Avoiding Duplicates
|
|
43
|
-
|
|
44
|
-
CRITICAL: Each task in an array must be semantically unique and provide distinct
|
|
45
|
-
value. Before returning multiple tasks, verify there are no duplicates.
|
|
46
|
-
|
|
47
|
-
Rules for preventing duplicates:
|
|
48
|
-
|
|
49
|
-
1. **Modifiers are not separate tasks**: Adverbs and adjectives that modify how
|
|
50
|
-
to perform a task are part of the task description, not separate tasks.
|
|
51
|
-
- "explain X in simple terms" = ONE task (not "explain X" + "use simple terms")
|
|
52
|
-
- "describe X in detail" = ONE task (not "describe X" + "make it detailed")
|
|
53
|
-
- "list X completely" = ONE task (not "list X" + "be complete")
|
|
54
|
-
|
|
55
|
-
2. **Synonymous verbs = duplicate**: Different verbs meaning the same thing with
|
|
56
|
-
the same object are duplicates. Keep only one or merge them.
|
|
57
|
-
- "explain X" + "describe X" = DUPLICATE (choose one)
|
|
58
|
-
- "show X" + "display X" = DUPLICATE (choose one)
|
|
59
|
-
- "check X" + "verify X" = DUPLICATE (choose one)
|
|
60
|
-
- "list X" + "enumerate X" = DUPLICATE (choose one)
|
|
61
|
-
|
|
62
|
-
3. **Tautological patterns stay single**: When a request uses a phrase that
|
|
63
|
-
already describes how to do something, don't split it.
|
|
64
|
-
- "explain Lehman's terms in Lehman's terms" = ONE task (the phrase already
|
|
65
|
-
means "in simple language")
|
|
66
|
-
- "describe it simply in simple words" = ONE task (redundant modifiers)
|
|
67
|
-
- "show clearly and display obviously" = ONE task (redundant verbs)
|
|
68
|
-
|
|
69
|
-
4. **Redundant operations**: If two alleged tasks would perform the same
|
|
70
|
-
operation, they're duplicates.
|
|
71
|
-
- "install and set up dependencies" = ONE task (setup is part of install)
|
|
72
|
-
- "check and verify disk space" = ONE task (verify means check)
|
|
73
|
-
- "list and show all files" = ONE task (list and show are the same)
|
|
74
|
-
|
|
75
|
-
## When NOT to Split
|
|
76
|
-
|
|
77
|
-
Keep as a single task when:
|
|
78
|
-
|
|
79
|
-
- **Single operation with modifiers**: "explain X in detail" (one action)
|
|
80
|
-
- **Tautological phrasing**: "do X in terms of X" (one action)
|
|
81
|
-
- **Redundant verb pairs**: "check and verify X" (same operation)
|
|
82
|
-
- **Compound modifiers**: "quickly and efficiently process X" (one action)
|
|
83
|
-
- **Implicit single operation**: "install dependencies" even if it involves
|
|
84
|
-
multiple steps internally
|
|
85
|
-
|
|
86
|
-
DO split when:
|
|
87
|
-
|
|
88
|
-
- **Distinct sequential operations**: "install deps, run tests" (two separate
|
|
89
|
-
commands)
|
|
90
|
-
- **Action + conditional**: "check disk space and warn if below 10%" (check,
|
|
91
|
-
then conditional action)
|
|
92
|
-
- **Different subjects**: "explain X and demonstrate Y" (two different things)
|
|
93
|
-
- **Truly separate steps**: "create file and add content to it" (two distinct
|
|
94
|
-
operations)
|
|
95
|
-
|
|
96
|
-
## Response Format
|
|
97
|
-
|
|
98
|
-
- Single task: Return ONLY the corrected command text
|
|
99
|
-
- Multiple tasks: Return ONLY a JSON array of strings
|
|
100
|
-
|
|
101
|
-
Do not include explanations, commentary, or any other text.
|
|
102
|
-
|
|
103
|
-
## Examples
|
|
104
|
-
|
|
105
|
-
### ❌ INCORRECT: Duplicate Tasks (What NOT to do)
|
|
106
|
-
|
|
107
|
-
These examples show common mistakes that create semantic duplicates:
|
|
108
|
-
|
|
109
|
-
- "explain Lehman's terms in Lehman's terms" →
|
|
110
|
-
❌ WRONG: ["explain what Lehman's terms are in simple language", "describe Lehman's terms using easy-to-understand words"]
|
|
111
|
-
✅ CORRECT: explain Lehman's terms in simple language
|
|
112
|
-
|
|
113
|
-
- "show and display files" →
|
|
114
|
-
❌ WRONG: ["show the files", "display the files"]
|
|
115
|
-
✅ CORRECT: show the files
|
|
116
|
-
|
|
117
|
-
- "check and verify disk space" →
|
|
118
|
-
❌ WRONG: ["check the disk space", "verify the disk space"]
|
|
119
|
-
✅ CORRECT: check the disk space
|
|
120
|
-
|
|
121
|
-
- "list directory contents completely" →
|
|
122
|
-
❌ WRONG: ["list the directory contents", "show all items"]
|
|
123
|
-
✅ CORRECT: list all directory contents
|
|
124
|
-
|
|
125
|
-
- "install and set up dependencies" →
|
|
126
|
-
❌ WRONG: ["install dependencies", "set up dependencies"]
|
|
127
|
-
✅ CORRECT: install dependencies
|
|
128
|
-
|
|
129
|
-
### ✅ CORRECT: Single Task
|
|
130
|
-
|
|
131
|
-
Simple requests should remain as single tasks:
|
|
132
|
-
|
|
133
|
-
- "change dir to ~" → change directory to the home folder
|
|
134
|
-
- "install deps" → install dependencies
|
|
135
|
-
- "make new file called test.txt" → create a new file called test.txt
|
|
136
|
-
- "show me files here" → show the files in the current directory
|
|
137
|
-
- "explain quantum physics simply" → explain quantum physics in simple terms
|
|
138
|
-
- "describe the process in detail" → describe the process in detail
|
|
139
|
-
- "check disk space thoroughly" → check the disk space thoroughly
|
|
140
|
-
|
|
141
|
-
### ✅ CORRECT: Multiple Tasks
|
|
142
|
-
|
|
143
|
-
Only split when tasks are truly distinct operations:
|
|
144
|
-
|
|
145
|
-
- "install deps, run tests" → ["install dependencies", "run tests"]
|
|
146
|
-
- "create file; add content" → ["create a file", "add content"]
|
|
147
|
-
- "build project and deploy" → ["build the project", "deploy"]
|
|
148
|
-
|
|
149
|
-
### ✅ CORRECT: Complex Questions (Split into Sequences)
|
|
150
|
-
|
|
151
|
-
Split only when multiple distinct queries or operations are needed:
|
|
152
|
-
|
|
153
|
-
- "tell me weather in Wro, is it over 70 deg" → ["show the weather in Wrocław", "check if the temperature is above 70 degrees"]
|
|
154
|
-
- "pls what is 7th prime and how many are to 1000" → ["find the 7th prime number", "count how many prime numbers are below 1000"]
|
|
155
|
-
- "check disk space and warn if below 10%" → ["check the disk space", "show a warning if it is below 10%"]
|
|
156
|
-
- "find config file and show its contents" → ["find the config file", "show its contents"]
|
package/dist/services/claude.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import { fileURLToPath } from 'url';
|
|
3
|
-
import { dirname, join } from 'path';
|
|
4
|
-
import Anthropic from '@anthropic-ai/sdk';
|
|
5
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
-
const __dirname = dirname(__filename);
|
|
7
|
-
const SYSTEM_PROMPT = readFileSync(join(__dirname, '../config/SYSTEM.md'), 'utf-8');
|
|
8
|
-
export class AnthropicClaudeService {
|
|
9
|
-
client;
|
|
10
|
-
model;
|
|
11
|
-
constructor(apiKey, model = 'claude-3-5-haiku-20241022') {
|
|
12
|
-
this.client = new Anthropic({ apiKey });
|
|
13
|
-
this.model = model;
|
|
14
|
-
}
|
|
15
|
-
async processCommand(rawCommand) {
|
|
16
|
-
const response = await this.client.messages.create({
|
|
17
|
-
model: this.model,
|
|
18
|
-
max_tokens: 200,
|
|
19
|
-
system: SYSTEM_PROMPT,
|
|
20
|
-
messages: [
|
|
21
|
-
{
|
|
22
|
-
role: 'user',
|
|
23
|
-
content: rawCommand,
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
});
|
|
27
|
-
const content = response.content[0];
|
|
28
|
-
if (content.type !== 'text') {
|
|
29
|
-
throw new Error('Unexpected response type from Claude API');
|
|
30
|
-
}
|
|
31
|
-
const text = content.text.trim();
|
|
32
|
-
// Try to parse as JSON array
|
|
33
|
-
if (text.startsWith('[') && text.endsWith(']')) {
|
|
34
|
-
try {
|
|
35
|
-
const parsed = JSON.parse(text);
|
|
36
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
37
|
-
// Validate all items are strings
|
|
38
|
-
const allStrings = parsed.every((item) => typeof item === 'string');
|
|
39
|
-
if (allStrings) {
|
|
40
|
-
return parsed.filter((item) => typeof item === 'string');
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// If JSON parsing fails, treat as single task
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Single task
|
|
49
|
-
return [text];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export function createClaudeService(apiKey) {
|
|
53
|
-
return new AnthropicClaudeService(apiKey);
|
|
54
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
export function CommandProcessor({ rawCommand, claudeService, }) {
|
|
5
|
-
const [processedTask, setProcessedTask] = useState(null);
|
|
6
|
-
const [error, setError] = useState(null);
|
|
7
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
8
|
-
useEffect(() => {
|
|
9
|
-
let mounted = true;
|
|
10
|
-
async function process() {
|
|
11
|
-
try {
|
|
12
|
-
const result = await claudeService.processCommand(rawCommand);
|
|
13
|
-
if (mounted) {
|
|
14
|
-
setProcessedTask(result);
|
|
15
|
-
setIsLoading(false);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
if (mounted) {
|
|
20
|
-
setError(err instanceof Error ? err.message : 'Unknown error occurred');
|
|
21
|
-
setIsLoading(false);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
process();
|
|
26
|
-
return () => {
|
|
27
|
-
mounted = false;
|
|
28
|
-
};
|
|
29
|
-
}, [rawCommand, claudeService]);
|
|
30
|
-
return (_jsxs(Box, { alignSelf: "flex-start", marginTop: 1, marginBottom: 1, flexDirection: "column", children: [_jsx(Box, { children: _jsxs(Text, { color: "gray", children: ["> pls ", rawCommand] }) }), isLoading && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "whiteBright", dimColor: true, children: "Processing..." }) })), error && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })), processedTask && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "whiteBright", dimColor: true, children: [' ⎿ ', "Task: \"", processedTask, "\""] }) }))] }));
|
|
31
|
-
}
|