@sysprompthub/cli 1.1.0 → 2.0.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.
- package/README.md +83 -41
- package/dist/commands/agent-add.js +50 -0
- package/dist/commands/agent-commands.test.js +119 -0
- package/dist/commands/agent-remove.js +31 -0
- package/dist/commands/assistant-clear.js +50 -0
- package/dist/commands/assistant-commands.test.js +97 -0
- package/dist/commands/assistant-set.js +38 -0
- package/dist/commands/base-command.js +1 -0
- package/dist/commands/claude.js +23 -0
- package/dist/commands/cli-integration.test.js +8 -0
- package/dist/commands/copilot.js +23 -0
- package/dist/commands/custom-add.js +48 -0
- package/dist/commands/custom-commands.test.js +122 -0
- package/dist/commands/custom-remove.js +25 -0
- package/dist/commands/custom.js +12 -0
- package/dist/commands/init.js +13 -164
- package/dist/commands/init.test.js +26 -77
- package/dist/commands/show.js +99 -0
- package/dist/commands/show.test.js +242 -0
- package/dist/commands/sync.js +12 -10
- package/dist/commands/sync.test.js +35 -25
- package/dist/config-helpers.js +35 -0
- package/dist/index.js +11 -1
- package/dist/version.js +1 -0
- package/package.json +14 -8
package/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# @sysprompthub/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Uplevel your AI development assistant with guardrails from [SysPromptHub](https://sysprompthub.com).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Now with support for **Claude Skills** and **GitHub Copilot Agents**!
|
|
6
|
+
|
|
7
|
+
> 🌐 For VS Code integration, see the [SysPromptHub Extension](https://marketplace.visualstudio.com/items?itemName=sysprompthub.sysprompthub)
|
|
8
|
+
>
|
|
9
|
+
> ⏳ Jetbrains IDE integration coming soon!
|
|
6
10
|
|
|
7
11
|
## Installation
|
|
8
12
|
|
|
@@ -10,70 +14,108 @@ For programmatic usage, see [@sysprompthub/sdk](../nodejs_sdk).
|
|
|
10
14
|
npm install -g @sysprompthub/cli
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
##
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- 🎯 **Multiple assistants** - Configure GitHub Copilot, Claude Desktop, and custom paths
|
|
20
|
+
- 🤖 **Agent/Skill support** - Manage specialized agents for different tasks
|
|
21
|
+
- 🔄 **Easy syncing** - Download and install prompts with one command
|
|
22
|
+
- 📝 **Interactive setup** - Guided configuration with search and selection
|
|
14
23
|
|
|
15
|
-
|
|
24
|
+
## Commands
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
### Initial setup
|
|
27
|
+
|
|
28
|
+
Initialize or update your development environment.
|
|
18
29
|
|
|
19
30
|
```bash
|
|
20
31
|
sysprompthub init
|
|
21
32
|
```
|
|
22
33
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
### Configure prompt packs for your assistant
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
sysprompthub copilot set sysprompthub/python/latest
|
|
38
|
+
sysprompthub claude skill add web-dev sysprompthub/react-tailwind/latest description="Skill to write frontend code" context=fork
|
|
39
|
+
```
|
|
40
|
+
|
|
28
41
|
|
|
29
|
-
### Sync
|
|
42
|
+
### Sync your prompt packs
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
Download and install configured prompts.
|
|
32
45
|
|
|
33
|
-
```
|
|
46
|
+
```shell
|
|
34
47
|
sysprompthub sync
|
|
35
48
|
```
|
|
36
49
|
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
### List your configured packs
|
|
51
|
+
|
|
52
|
+
Display all configured packs for your project.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sysprompthub show
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
## Assistant Commands
|
|
60
|
+
|
|
61
|
+
### GitHub Copilot
|
|
62
|
+
|
|
63
|
+
See `sysprompthub copilot --help` for more details.
|
|
39
64
|
|
|
40
|
-
|
|
65
|
+
**Example:**
|
|
66
|
+
```bash
|
|
67
|
+
# Set main prompt
|
|
68
|
+
sysprompthub copilot set owner/general/latest
|
|
41
69
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
46
|
-
"apiKey": "your-40-character-api-key"
|
|
47
|
-
}
|
|
70
|
+
# Add specialized agents
|
|
71
|
+
sysprompthub copilot agent add python owner/python-expert/v1.0.0
|
|
72
|
+
sysprompthub copilot agent add typescript owner/ts-expert/latest description="My typescript agent"
|
|
48
73
|
```
|
|
49
74
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
### Claude Desktop
|
|
76
|
+
|
|
77
|
+
See `sysprompthub claude --help` for more details.
|
|
78
|
+
|
|
79
|
+
**Example:**
|
|
80
|
+
```bash
|
|
81
|
+
# Set main prompt
|
|
82
|
+
sysprompthub claude set owner/general/latest
|
|
83
|
+
|
|
84
|
+
# Add skills
|
|
85
|
+
sysprompthub claude skill add angular owner/angular-expert/v2.0.0 description="Skill to write angular code"
|
|
86
|
+
sysprompthub claude skill remove angular
|
|
58
87
|
```
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
### Custom Paths
|
|
90
|
+
|
|
91
|
+
See `sysprompthub custom --help` for more details.
|
|
92
|
+
|
|
93
|
+
**Example:**
|
|
94
|
+
```bash
|
|
95
|
+
sysprompthub custom add docs/ai-prompt.md someone/markdown/latest
|
|
96
|
+
sysprompthub custom remove docs/ai-prompt.md
|
|
66
97
|
```
|
|
67
98
|
|
|
68
|
-
##
|
|
99
|
+
## Configuration
|
|
69
100
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
101
|
+
Project configuration is stored in the `.sysprompthub.json` file in the root of your project.
|
|
102
|
+
|
|
103
|
+
User configuration is stored in the `~/.sysprompthub` directory.
|
|
104
|
+
|
|
105
|
+
## Programmatic Usage
|
|
106
|
+
|
|
107
|
+
For Node.js/TypeScript integration, use [@sysprompthub/sdk](https://www.npmjs.com/package/@sysprompthub/sdk).
|
|
108
|
+
|
|
109
|
+
## Getting Help
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Show all commands
|
|
113
|
+
sysprompthub --help
|
|
114
|
+
```
|
|
73
115
|
|
|
74
116
|
## Contributing
|
|
75
117
|
|
|
76
|
-
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development
|
|
118
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
|
|
77
119
|
|
|
78
120
|
## License
|
|
79
121
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadProjectConfigEditor, validatePackName, validateAgentName, parseFrontmatterArg } from '../config-helpers.js';
|
|
3
|
+
import { BaseCommand } from "./base-command.js";
|
|
4
|
+
export class AgentAddCommand extends BaseCommand {
|
|
5
|
+
assistant;
|
|
6
|
+
agentTerm;
|
|
7
|
+
constructor(assistant, agentTerm) {
|
|
8
|
+
super();
|
|
9
|
+
this.assistant = assistant;
|
|
10
|
+
this.agentTerm = agentTerm;
|
|
11
|
+
}
|
|
12
|
+
async run({ name, pack, environment, frontmatter }) {
|
|
13
|
+
// Validate agent name
|
|
14
|
+
if (!validateAgentName(name)) {
|
|
15
|
+
console.error(`Error: Invalid ${this.agentTerm} name: ${name}`);
|
|
16
|
+
console.error('Name must contain only lowercase letters, numbers, hyphens, and underscores');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// Validate pack format
|
|
21
|
+
if (!validatePackName(pack)) {
|
|
22
|
+
console.error(`Error: Invalid pack name: ${pack}`);
|
|
23
|
+
console.error('Expected format: owner/pack/version');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Load project config editor
|
|
28
|
+
const editor = await loadProjectConfigEditor();
|
|
29
|
+
// Add agent using editor
|
|
30
|
+
await editor.addAgent({
|
|
31
|
+
assistant: this.assistant,
|
|
32
|
+
name,
|
|
33
|
+
pack,
|
|
34
|
+
frontMatter: frontmatter,
|
|
35
|
+
environment
|
|
36
|
+
});
|
|
37
|
+
console.log(`✓ ${this.assistant} ${this.agentTerm} '${name}' added with pack ${pack}`);
|
|
38
|
+
}
|
|
39
|
+
create() {
|
|
40
|
+
const command = new Command('add');
|
|
41
|
+
command
|
|
42
|
+
.description(`Add a ${this.assistant} ${this.agentTerm}`)
|
|
43
|
+
.argument('<name>', `${this.agentTerm} name (lowercase, alphanumeric, hyphens, underscores)`)
|
|
44
|
+
.argument('<pack>', 'Pack name (owner/pack/version)')
|
|
45
|
+
.argument('[frontmatter...]', 'Frontmatter key-value pairs (e.g. description="My agent")', parseFrontmatterArg)
|
|
46
|
+
.addOption(AgentAddCommand.envOption)
|
|
47
|
+
.action(async (name, pack, frontmatter, options) => await this.run({ name, pack, frontmatter, environment: options.environment }));
|
|
48
|
+
return command;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { AgentAddCommand } from './agent-add.js';
|
|
3
|
+
import { AgentRemoveCommand } from './agent-remove.js';
|
|
4
|
+
vi.mock('../config-helpers.js', () => ({
|
|
5
|
+
loadProjectConfigEditor: vi.fn(),
|
|
6
|
+
validatePackName: vi.fn(),
|
|
7
|
+
validateAgentName: vi.fn(),
|
|
8
|
+
parseFrontmatterArg: vi.fn()
|
|
9
|
+
}));
|
|
10
|
+
describe('AgentAddCommand', () => {
|
|
11
|
+
let command;
|
|
12
|
+
let helpers;
|
|
13
|
+
let mockEditor;
|
|
14
|
+
let consoleErrorSpy;
|
|
15
|
+
let consoleLogSpy;
|
|
16
|
+
let consoleWarnSpy;
|
|
17
|
+
let processExitSpy;
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
helpers = await import('../config-helpers.js');
|
|
20
|
+
vi.clearAllMocks();
|
|
21
|
+
mockEditor = {
|
|
22
|
+
packs: [],
|
|
23
|
+
addAgent: vi.fn()
|
|
24
|
+
};
|
|
25
|
+
vi.mocked(helpers.loadProjectConfigEditor).mockResolvedValue(mockEditor);
|
|
26
|
+
vi.mocked(helpers.validatePackName).mockReturnValue(true);
|
|
27
|
+
vi.mocked(helpers.validateAgentName).mockReturnValue(true);
|
|
28
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
29
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
30
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
31
|
+
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
32
|
+
command = new AgentAddCommand('copilot', 'agent');
|
|
33
|
+
});
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
consoleErrorSpy.mockRestore();
|
|
36
|
+
consoleLogSpy.mockRestore();
|
|
37
|
+
consoleWarnSpy.mockRestore();
|
|
38
|
+
processExitSpy.mockRestore();
|
|
39
|
+
});
|
|
40
|
+
describe('run', () => {
|
|
41
|
+
it('should add new agent', async () => {
|
|
42
|
+
await command.run({ name: 'python', pack: 'owner/pack/latest', frontmatter: {}, environment: undefined });
|
|
43
|
+
expect(helpers.validateAgentName).toHaveBeenCalledWith('python');
|
|
44
|
+
expect(helpers.validatePackName).toHaveBeenCalledWith('owner/pack/latest');
|
|
45
|
+
expect(mockEditor.addAgent).toHaveBeenCalledWith({
|
|
46
|
+
assistant: 'copilot',
|
|
47
|
+
name: 'python',
|
|
48
|
+
pack: 'owner/pack/latest',
|
|
49
|
+
frontMatter: {},
|
|
50
|
+
environment: undefined
|
|
51
|
+
});
|
|
52
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("✓ copilot agent 'python' added with pack owner/pack/latest");
|
|
53
|
+
});
|
|
54
|
+
it('should exit with error on invalid agent name', async () => {
|
|
55
|
+
vi.mocked(helpers.validateAgentName).mockReturnValue(false);
|
|
56
|
+
await command.run({ name: 'Invalid-Name', pack: 'owner/pack/latest', frontmatter: {}, environment: undefined });
|
|
57
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Invalid agent name: Invalid-Name');
|
|
58
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
59
|
+
expect(mockEditor.addAgent).not.toHaveBeenCalled();
|
|
60
|
+
});
|
|
61
|
+
it('should exit with error on invalid pack name', async () => {
|
|
62
|
+
vi.mocked(helpers.validatePackName).mockReturnValue(false);
|
|
63
|
+
await command.run({ name: 'python', pack: 'invalid-pack', frontmatter: {}, environment: undefined });
|
|
64
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Invalid pack name: invalid-pack');
|
|
65
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
66
|
+
expect(mockEditor.addAgent).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('AgentRemoveCommand', () => {
|
|
71
|
+
let command;
|
|
72
|
+
let helpers;
|
|
73
|
+
let mockEditor;
|
|
74
|
+
let consoleErrorSpy;
|
|
75
|
+
let consoleLogSpy;
|
|
76
|
+
let consoleWarnSpy;
|
|
77
|
+
let processExitSpy;
|
|
78
|
+
beforeEach(async () => {
|
|
79
|
+
helpers = await import('../config-helpers.js');
|
|
80
|
+
vi.clearAllMocks();
|
|
81
|
+
mockEditor = {
|
|
82
|
+
packs: [],
|
|
83
|
+
removeAgent: vi.fn()
|
|
84
|
+
};
|
|
85
|
+
vi.mocked(helpers.loadProjectConfigEditor).mockResolvedValue(mockEditor);
|
|
86
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
87
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
88
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
89
|
+
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
90
|
+
command = new AgentRemoveCommand('copilot', 'agent');
|
|
91
|
+
});
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
consoleErrorSpy.mockRestore();
|
|
94
|
+
consoleLogSpy.mockRestore();
|
|
95
|
+
consoleWarnSpy.mockRestore();
|
|
96
|
+
processExitSpy.mockRestore();
|
|
97
|
+
});
|
|
98
|
+
describe('run', () => {
|
|
99
|
+
it('should remove agent', async () => {
|
|
100
|
+
vi.mocked(mockEditor.removeAgent).mockResolvedValue(true);
|
|
101
|
+
await command.run('python');
|
|
102
|
+
expect(mockEditor.removeAgent).toHaveBeenCalledWith('copilot', 'python');
|
|
103
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("✓ copilot agent 'python' removed");
|
|
104
|
+
});
|
|
105
|
+
it('should exit with error when agent not found', async () => {
|
|
106
|
+
vi.mocked(mockEditor.removeAgent).mockResolvedValue(false);
|
|
107
|
+
await command.run('python');
|
|
108
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith("Error: copilot agent 'python' not found");
|
|
109
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
110
|
+
});
|
|
111
|
+
it('should warn when file deletion fails', async () => {
|
|
112
|
+
const error = new Error('File not found');
|
|
113
|
+
vi.mocked(mockEditor.removeAgent).mockResolvedValue(error);
|
|
114
|
+
await command.run('python');
|
|
115
|
+
expect(consoleLogSpy).toHaveBeenCalledWith("✓ copilot agent 'python' removed");
|
|
116
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('⚠ Warning: Could not delete prompt file: File not found');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadProjectConfigEditor } from '../config-helpers.js';
|
|
3
|
+
export class AgentRemoveCommand {
|
|
4
|
+
assistant;
|
|
5
|
+
agentTerm;
|
|
6
|
+
constructor(assistant, agentTerm) {
|
|
7
|
+
this.assistant = assistant;
|
|
8
|
+
this.agentTerm = agentTerm;
|
|
9
|
+
}
|
|
10
|
+
async run(name) {
|
|
11
|
+
const editor = await loadProjectConfigEditor();
|
|
12
|
+
const result = await editor.removeAgent(this.assistant, name);
|
|
13
|
+
if (result === false) {
|
|
14
|
+
console.error(`Error: ${this.assistant} ${this.agentTerm} '${name}' not found`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.log(`✓ ${this.assistant} ${this.agentTerm} '${name}' removed`);
|
|
19
|
+
if (result !== true) {
|
|
20
|
+
console.warn(`⚠ Warning: Could not delete prompt file: ${result.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
create() {
|
|
24
|
+
const command = new Command('remove');
|
|
25
|
+
command
|
|
26
|
+
.description(`Remove a ${this.assistant} ${this.agentTerm}`)
|
|
27
|
+
.argument('<name>', `${this.agentTerm} name`)
|
|
28
|
+
.action(async (name) => await this.run(name));
|
|
29
|
+
return command;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadProjectConfigEditor } from '../config-helpers.js';
|
|
3
|
+
export class AssistantClearCommand {
|
|
4
|
+
assistant;
|
|
5
|
+
constructor(assistant) {
|
|
6
|
+
this.assistant = assistant;
|
|
7
|
+
}
|
|
8
|
+
async run(options) {
|
|
9
|
+
const editor = await loadProjectConfigEditor();
|
|
10
|
+
if (!editor.packs || editor.packs.length === 0) {
|
|
11
|
+
console.log(`No ${this.assistant} packs configured`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (options.all) {
|
|
15
|
+
// Count packs for this assistant
|
|
16
|
+
const count = editor.packs.filter(p => p.type === this.assistant).length;
|
|
17
|
+
if (count === 0) {
|
|
18
|
+
console.log(`No ${this.assistant} packs found`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const errors = await editor.clearAll(this.assistant);
|
|
22
|
+
console.log(`✓ Cleared all ${this.assistant} packs (${count} removed)`);
|
|
23
|
+
if (errors.length > 0) {
|
|
24
|
+
console.warn(`⚠ Warning: Could not delete ${errors.length} prompt file(s)`);
|
|
25
|
+
for (const error of errors) {
|
|
26
|
+
console.warn(` ${error.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
const result = await editor.clear(this.assistant);
|
|
32
|
+
if (result === false) {
|
|
33
|
+
console.log(`No ${this.assistant} primary pack found`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
console.log(`✓ Cleared ${this.assistant} primary pack`);
|
|
37
|
+
if (result !== true) {
|
|
38
|
+
console.warn(`⚠ Warning: Could not delete prompt file: ${result.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
create() {
|
|
43
|
+
const command = new Command('clear');
|
|
44
|
+
command
|
|
45
|
+
.description(`Clear primary pack for ${this.assistant}`)
|
|
46
|
+
.option('-a, --all', 'Also remove all agents/skills')
|
|
47
|
+
.action(async (options) => await this.run(options));
|
|
48
|
+
return command;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { AssistantSetCommand } from './assistant-set.js';
|
|
3
|
+
import { AssistantClearCommand } from './assistant-clear.js';
|
|
4
|
+
vi.mock('../config-helpers.js', () => ({
|
|
5
|
+
loadProjectConfigEditor: vi.fn(),
|
|
6
|
+
validatePackName: vi.fn()
|
|
7
|
+
}));
|
|
8
|
+
describe('AssistantSetCommand', () => {
|
|
9
|
+
let command;
|
|
10
|
+
let helpers;
|
|
11
|
+
let mockEditor;
|
|
12
|
+
let consoleErrorSpy;
|
|
13
|
+
let consoleLogSpy;
|
|
14
|
+
let processExitSpy;
|
|
15
|
+
beforeEach(async () => {
|
|
16
|
+
helpers = await import('../config-helpers.js');
|
|
17
|
+
vi.clearAllMocks();
|
|
18
|
+
mockEditor = {
|
|
19
|
+
packs: [],
|
|
20
|
+
set: vi.fn()
|
|
21
|
+
};
|
|
22
|
+
vi.mocked(helpers.loadProjectConfigEditor).mockResolvedValue(mockEditor);
|
|
23
|
+
vi.mocked(helpers.validatePackName).mockReturnValue(true);
|
|
24
|
+
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
25
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
26
|
+
processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
|
|
27
|
+
command = new AssistantSetCommand('copilot');
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
consoleErrorSpy.mockRestore();
|
|
31
|
+
consoleLogSpy.mockRestore();
|
|
32
|
+
processExitSpy.mockRestore();
|
|
33
|
+
});
|
|
34
|
+
describe('run', () => {
|
|
35
|
+
it('should set assistant pack', async () => {
|
|
36
|
+
await command.run('owner/pack/latest', {});
|
|
37
|
+
expect(helpers.validatePackName).toHaveBeenCalledWith('owner/pack/latest');
|
|
38
|
+
expect(mockEditor.set).toHaveBeenCalledWith({
|
|
39
|
+
assistant: 'copilot',
|
|
40
|
+
pack: 'owner/pack/latest',
|
|
41
|
+
environment: undefined
|
|
42
|
+
});
|
|
43
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('✓ copilot primary pack set to owner/pack/latest');
|
|
44
|
+
});
|
|
45
|
+
it('should exit with error on invalid pack name', async () => {
|
|
46
|
+
vi.mocked(helpers.validatePackName).mockReturnValue(false);
|
|
47
|
+
await command.run('invalid-pack', {});
|
|
48
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Invalid pack name: invalid-pack');
|
|
49
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
50
|
+
expect(mockEditor.set).not.toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('AssistantClearCommand', () => {
|
|
55
|
+
let command;
|
|
56
|
+
let helpers;
|
|
57
|
+
let mockEditor;
|
|
58
|
+
let consoleLogSpy;
|
|
59
|
+
let consoleWarnSpy;
|
|
60
|
+
beforeEach(async () => {
|
|
61
|
+
helpers = await import('../config-helpers.js');
|
|
62
|
+
vi.clearAllMocks();
|
|
63
|
+
mockEditor = {
|
|
64
|
+
packs: [{ type: 'copilot', pack: 'owner/pack/latest' }],
|
|
65
|
+
clear: vi.fn(),
|
|
66
|
+
clearAll: vi.fn()
|
|
67
|
+
};
|
|
68
|
+
vi.mocked(helpers.loadProjectConfigEditor).mockResolvedValue(mockEditor);
|
|
69
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
70
|
+
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
71
|
+
command = new AssistantClearCommand('copilot');
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
consoleLogSpy.mockRestore();
|
|
75
|
+
consoleWarnSpy.mockRestore();
|
|
76
|
+
});
|
|
77
|
+
describe('run', () => {
|
|
78
|
+
it('should clear root pack', async () => {
|
|
79
|
+
vi.mocked(mockEditor.clear).mockResolvedValue(true);
|
|
80
|
+
await command.run({});
|
|
81
|
+
expect(mockEditor.clear).toHaveBeenCalledWith('copilot');
|
|
82
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('✓ Cleared copilot primary pack');
|
|
83
|
+
});
|
|
84
|
+
it('should clear all packs with --all', async () => {
|
|
85
|
+
vi.mocked(mockEditor.clearAll).mockResolvedValue([]);
|
|
86
|
+
await command.run({ all: true });
|
|
87
|
+
expect(mockEditor.clearAll).toHaveBeenCalledWith('copilot');
|
|
88
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('✓ Cleared all copilot packs (1 removed)');
|
|
89
|
+
});
|
|
90
|
+
it('should warn on file deletion errors', async () => {
|
|
91
|
+
const error = new Error('File not found');
|
|
92
|
+
vi.mocked(mockEditor.clear).mockResolvedValue(error);
|
|
93
|
+
await command.run({});
|
|
94
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith('⚠ Warning: Could not delete prompt file: File not found');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { loadProjectConfigEditor, validatePackName } from '../config-helpers.js';
|
|
3
|
+
export class AssistantSetCommand {
|
|
4
|
+
assistant;
|
|
5
|
+
static envOption = new Option('-e, --environment [env]')
|
|
6
|
+
.choices(['production', 'development', 'local'])
|
|
7
|
+
.env('SYSPROMPTHUB_ENVIRONMENT');
|
|
8
|
+
constructor(assistant) {
|
|
9
|
+
this.assistant = assistant;
|
|
10
|
+
}
|
|
11
|
+
async run(pack, options) {
|
|
12
|
+
// Validate pack format
|
|
13
|
+
if (!validatePackName(pack)) {
|
|
14
|
+
console.error(`Error: Invalid pack name: ${pack}`);
|
|
15
|
+
console.error('Expected format: owner/pack/version');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Load project config editor
|
|
20
|
+
const editor = await loadProjectConfigEditor();
|
|
21
|
+
// Set the pack using editor
|
|
22
|
+
await editor.set({
|
|
23
|
+
assistant: this.assistant,
|
|
24
|
+
pack,
|
|
25
|
+
environment: options.environment
|
|
26
|
+
});
|
|
27
|
+
console.log(`✓ ${this.assistant} primary pack set to ${pack}`);
|
|
28
|
+
}
|
|
29
|
+
create() {
|
|
30
|
+
const command = new Command('set');
|
|
31
|
+
command
|
|
32
|
+
.description(`Set primary pack for ${this.assistant}`)
|
|
33
|
+
.argument('<pack>', 'Pack name (owner/pack/version)')
|
|
34
|
+
.addOption(AssistantSetCommand.envOption)
|
|
35
|
+
.action(async (pack, options) => await this.run(pack, options));
|
|
36
|
+
return command;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { AssistantSetCommand } from './assistant-set.js';
|
|
3
|
+
import { AssistantClearCommand } from './assistant-clear.js';
|
|
4
|
+
import { AgentAddCommand } from './agent-add.js';
|
|
5
|
+
import { AgentRemoveCommand } from './agent-remove.js';
|
|
6
|
+
export function createClaudeCommand() {
|
|
7
|
+
const claude = new Command('claude');
|
|
8
|
+
claude.description('Manage Claude configuration');
|
|
9
|
+
// Add set/clear commands
|
|
10
|
+
const setCmd = new AssistantSetCommand('claude');
|
|
11
|
+
const clearCmd = new AssistantClearCommand('claude');
|
|
12
|
+
claude.addCommand(setCmd.create());
|
|
13
|
+
claude.addCommand(clearCmd.create());
|
|
14
|
+
// Add skill subcommand group
|
|
15
|
+
const skill = new Command('skill');
|
|
16
|
+
skill.description('Manage Claude skills');
|
|
17
|
+
const addCmd = new AgentAddCommand('claude', 'skill');
|
|
18
|
+
const removeCmd = new AgentRemoveCommand('claude', 'skill');
|
|
19
|
+
skill.addCommand(addCmd.create());
|
|
20
|
+
skill.addCommand(removeCmd.create());
|
|
21
|
+
claude.addCommand(skill);
|
|
22
|
+
return claude;
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { AssistantSetCommand } from './assistant-set.js';
|
|
3
|
+
import { AssistantClearCommand } from './assistant-clear.js';
|
|
4
|
+
import { AgentAddCommand } from './agent-add.js';
|
|
5
|
+
import { AgentRemoveCommand } from './agent-remove.js';
|
|
6
|
+
export function createCopilotCommand() {
|
|
7
|
+
const copilot = new Command('copilot');
|
|
8
|
+
copilot.description('Manage GitHub Copilot configuration');
|
|
9
|
+
// Add set/clear commands
|
|
10
|
+
const setCmd = new AssistantSetCommand('copilot');
|
|
11
|
+
const clearCmd = new AssistantClearCommand('copilot');
|
|
12
|
+
copilot.addCommand(setCmd.create());
|
|
13
|
+
copilot.addCommand(clearCmd.create());
|
|
14
|
+
// Add agent subcommand group
|
|
15
|
+
const agent = new Command('agent');
|
|
16
|
+
agent.description('Manage Copilot agents');
|
|
17
|
+
const addCmd = new AgentAddCommand('copilot', 'agent');
|
|
18
|
+
const removeCmd = new AgentRemoveCommand('copilot', 'agent');
|
|
19
|
+
agent.addCommand(addCmd.create());
|
|
20
|
+
agent.addCommand(removeCmd.create());
|
|
21
|
+
copilot.addCommand(agent);
|
|
22
|
+
return copilot;
|
|
23
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { loadProjectConfigEditor, validatePackName } from '../config-helpers.js';
|
|
3
|
+
import { isAbsolute } from 'path';
|
|
4
|
+
import isValidPath from 'is-valid-path';
|
|
5
|
+
export class CustomAddCommand {
|
|
6
|
+
static envOption = new Option('-e, --environment [env]')
|
|
7
|
+
.choices(['production', 'development', 'local'])
|
|
8
|
+
.env('SYSPROMPTHUB_ENVIRONMENT');
|
|
9
|
+
async run(path, pack, options) {
|
|
10
|
+
// Validate path
|
|
11
|
+
if (isAbsolute(path)) {
|
|
12
|
+
console.error('Error: Path must be relative to the current working directory');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!isValidPath(path)) {
|
|
17
|
+
console.error('Error: Invalid path format');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Validate pack format
|
|
22
|
+
if (!validatePackName(pack)) {
|
|
23
|
+
console.error(`Error: Invalid pack name: ${pack}`);
|
|
24
|
+
console.error('Expected format: owner/pack/version');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Load project config editor
|
|
29
|
+
const editor = await loadProjectConfigEditor();
|
|
30
|
+
// Add custom pack using editor
|
|
31
|
+
await editor.addCustom({
|
|
32
|
+
path,
|
|
33
|
+
pack,
|
|
34
|
+
environment: options.environment
|
|
35
|
+
});
|
|
36
|
+
console.log(`✓ Custom pack added: ${path} -> ${pack}`);
|
|
37
|
+
}
|
|
38
|
+
create() {
|
|
39
|
+
const command = new Command('add');
|
|
40
|
+
command
|
|
41
|
+
.description('Add a custom path configuration')
|
|
42
|
+
.argument('<path>', 'Relative path where prompt will be written')
|
|
43
|
+
.argument('<pack>', 'Pack name (owner/pack/version)')
|
|
44
|
+
.addOption(CustomAddCommand.envOption)
|
|
45
|
+
.action(async (path, pack, options) => await this.run(path, pack, options));
|
|
46
|
+
return command;
|
|
47
|
+
}
|
|
48
|
+
}
|