prompt-language-shell 0.2.6 → 0.2.9

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,82 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { mergeConfig } from './config.js';
3
- describe('mergeConfig', () => {
4
- it('creates new config when file is empty', () => {
5
- const result = mergeConfig('', 'anthropic', {
6
- 'api-key': 'sk-ant-test',
7
- model: 'claude-haiku-4-5-20251001',
8
- });
9
- expect(result).toContain('anthropic:');
10
- expect(result).toContain(' api-key: sk-ant-test');
11
- expect(result).toContain(' model: claude-haiku-4-5-20251001');
12
- });
13
- it('adds new section to existing config', () => {
14
- const existing = `ui:
15
- theme: dark
16
- verbose: true`;
17
- const result = mergeConfig(existing, 'anthropic', {
18
- 'api-key': 'sk-ant-test',
19
- model: 'claude-haiku-4-5-20251001',
20
- });
21
- expect(result).toContain('ui:');
22
- expect(result).toContain(' theme: dark');
23
- expect(result).toContain('anthropic:');
24
- expect(result).toContain(' api-key: sk-ant-test');
25
- });
26
- it('sorts sections alphabetically', () => {
27
- const existing = `ui:
28
- theme: dark`;
29
- const result = mergeConfig(existing, 'anthropic', {
30
- 'api-key': 'sk-ant-test',
31
- });
32
- const anthropicIndex = result.indexOf('anthropic:');
33
- const uiIndex = result.indexOf('ui:');
34
- expect(anthropicIndex).toBeLessThan(uiIndex);
35
- });
36
- it('updates existing section without removing other keys', () => {
37
- const existing = `anthropic:
38
- api-key: sk-ant-old
39
- custom-setting: value`;
40
- const result = mergeConfig(existing, 'anthropic', {
41
- 'api-key': 'sk-ant-new',
42
- model: 'claude-haiku-4-5-20251001',
43
- });
44
- expect(result).toContain('api-key: sk-ant-new');
45
- expect(result).toContain('model: claude-haiku-4-5-20251001');
46
- expect(result).toContain('custom-setting: value');
47
- expect(result).not.toContain('sk-ant-old');
48
- });
49
- it('adds empty line before new section', () => {
50
- const existing = `ui:
51
- theme: dark`;
52
- const result = mergeConfig(existing, 'anthropic', {
53
- 'api-key': 'sk-ant-test',
54
- });
55
- expect(result).toMatch(/anthropic:\n {2}api-key:/);
56
- expect(result).toMatch(/ui:\n {2}theme:/);
57
- });
58
- it('handles multiple sections with sorting', () => {
59
- const existing = `ui:
60
- theme: dark
61
-
62
- config:
63
- llm: anthropic
64
- name: Sensei`;
65
- const result = mergeConfig(existing, 'anthropic', {
66
- 'api-key': 'sk-ant-test',
67
- });
68
- const sections = result.match(/^[a-z]+:/gm) || [];
69
- expect(sections).toEqual(['anthropic:', 'config:', 'ui:']);
70
- });
71
- it('updates key in existing section', () => {
72
- const existing = `anthropic:
73
- api-key: sk-ant-old
74
- model: old-model`;
75
- const result = mergeConfig(existing, 'anthropic', {
76
- model: 'new-model',
77
- });
78
- expect(result).toContain('api-key: sk-ant-old');
79
- expect(result).toContain('model: new-model');
80
- expect(result).not.toContain('old-model');
81
- });
82
- });
@@ -1,115 +0,0 @@
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,20 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } 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 ConfigSetup({ onComplete }) {
6
- const [step, setStep] = React.useState('api-key');
7
- const [apiKey, setApiKey] = React.useState('');
8
- const [model, setModel] = React.useState('claude-haiku-4-5-20251001');
9
- const handleApiKeySubmit = (value) => {
10
- setApiKey(value);
11
- setStep('model');
12
- };
13
- const handleModelSubmit = (value) => {
14
- const finalModel = value.trim() || 'claude-haiku-4-5-20251001';
15
- setModel(finalModel);
16
- setStep('done');
17
- onComplete({ apiKey, model: finalModel });
18
- };
19
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsx(Text, { children: "Configuration required." }), _jsx(Box, { children: _jsxs(Text, { color: "whiteBright", dimColor: true, children: ["==>", " Get your API key from: https://platform.claude.com/"] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: "Anthropic API key:" }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'api-key' ? (_jsx(TextInput, { value: apiKey, onChange: setApiKey, onSubmit: handleApiKeySubmit, mask: "*" })) : (_jsx(Text, { dimColor: true, children: '*'.repeat(12) }))] }), (step === 'model' || step === 'done') && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { children: ["Model ", _jsx(Text, { dimColor: true, children: "(default: claude-haiku-4-5-20251001)" }), ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'model' ? (_jsx(TextInput, { value: model, onChange: setModel, onSubmit: handleModelSubmit })) : (_jsx(Text, { dimColor: true, children: model }))] })] })), step === 'done' && (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: "green", children: "\u2713 Configuration saved" }) }))] }));
20
- }
@@ -1,16 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box } from 'ink';
4
- import { createAnthropicService } from '../services/anthropic.js';
5
- import { ConfigSetup } from './ConfigSetup.js';
6
- import { Command } from './Command.js';
7
- export function ConfigThenCommand({ command, onConfigSave, }) {
8
- const [configComplete, setConfigComplete] = React.useState(false);
9
- const [savedConfig, setSavedConfig] = React.useState(null);
10
- const handleConfigComplete = (config) => {
11
- onConfigSave(config.apiKey, config.model);
12
- setSavedConfig(config);
13
- setConfigComplete(true);
14
- };
15
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsx(ConfigSetup, { onComplete: handleConfigComplete }), configComplete && savedConfig && (_jsx(Command, { rawCommand: command, claudeService: createAnthropicService(savedConfig.apiKey, savedConfig.model) }))] }));
16
- }
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } 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 Configure({ state, key: keyProp, model: modelProp, onComplete, }) {
6
- const done = state?.done ?? false;
7
- const [step, setStep] = React.useState(state?.step ?? (done ? 'done' : 'key'));
8
- const [key, setKey] = React.useState(keyProp || '');
9
- const [model, setModel] = React.useState(modelProp || 'claude-haiku-4-5-20251001');
10
- const handleKeySubmit = (value) => {
11
- setKey(value);
12
- setStep('model');
13
- };
14
- const handleModelSubmit = (value) => {
15
- const finalModel = value.trim() || 'claude-haiku-4-5-20251001';
16
- setModel(finalModel);
17
- setStep('done');
18
- if (onComplete) {
19
- onComplete({ key, model: finalModel });
20
- }
21
- };
22
- return (_jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [!done && _jsx(Text, { children: "Configuration required." }), !done && (_jsx(Box, { children: _jsxs(Text, { color: "whiteBright", dimColor: true, children: ['==>', " Get your API key from: https://platform.claude.com/"] }) })), _jsx(Box, { marginTop: done ? 0 : 1, children: _jsx(Text, { children: "Anthropic API key:" }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'key' && !done ? (_jsx(TextInput, { value: key, onChange: setKey, onSubmit: handleKeySubmit, mask: "*" })) : (_jsx(Text, { dimColor: true, children: '*'.repeat(12) }))] }), (step === 'model' || step === 'done') && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Box, { children: _jsxs(Text, { children: ["Model", ' ', !done && (_jsx(Text, { dimColor: true, children: "(default: claude-haiku-4-5-20251001)" })), ":"] }) }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "> " }), step === 'model' && !done ? (_jsx(TextInput, { value: model, onChange: setModel, onSubmit: handleModelSubmit })) : (_jsx(Text, { dimColor: true, children: model }))] })] })), step === 'done' && !done && (_jsx(Box, { marginY: 1, children: _jsx(Text, { color: "green", children: "\u2713 Configuration saved" }) }))] }));
23
- }
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Box } from 'ink';
3
- import { renderComponent } from './renderComponent.js';
4
- export function History({ items }) {
5
- if (items.length === 0) {
6
- return null;
7
- }
8
- return (_jsx(Box, { flexDirection: "column", gap: 1, children: items.map((item, index) => (_jsx(Box, { children: renderComponent(item) }, `${item.name}-${index}`))) }));
9
- }
package/dist/ui/Please.js DELETED
@@ -1,25 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box } from 'ink';
4
- import { Command } from './Command.js';
5
- import { Welcome } from './Welcome.js';
6
- import { ConfigSetup } from './ConfigSetup.js';
7
- import { History } from './History.js';
8
- export const PLS = ({ app: info, command, claudeService, showConfigSetup, onConfigComplete, }) => {
9
- const [history, setHistory] = React.useState([]);
10
- const [service, setService] = React.useState(claudeService);
11
- const handleConfigComplete = (config) => {
12
- if (onConfigComplete) {
13
- const result = onConfigComplete(config);
14
- if (result) {
15
- setService(result);
16
- }
17
- }
18
- };
19
- // Command execution (with service from props or after config)
20
- if (command && service) {
21
- return (_jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsx(History, { items: history }), _jsx(Command, { rawCommand: command, claudeService: service })] }));
22
- }
23
- // Welcome screen with optional config setup
24
- return (_jsxs(Box, { flexDirection: "column", marginY: 1, gap: 1, children: [_jsx(History, { items: history }), !showConfigSetup && _jsx(Welcome, { info: info }), showConfigSetup && _jsx(ConfigSetup, { onComplete: handleConfigComplete })] }));
25
- };
@@ -1,14 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Welcome } from './Welcome.js';
3
- import { Config } from './Config.js';
4
- import { Command } from './Command.js';
5
- export function renderComponent(def) {
6
- switch (def.name) {
7
- case 'welcome':
8
- return _jsx(Welcome, { ...def.props });
9
- case 'config':
10
- return (_jsx(Config, { ...def.props, state: 'state' in def ? def.state : undefined }));
11
- case 'command':
12
- return (_jsx(Command, { ...def.props, state: 'state' in def ? def.state : undefined }));
13
- }
14
- }