@sysprompthub/cli 1.1.1 → 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 CHANGED
@@ -1,8 +1,12 @@
1
1
  # @sysprompthub/cli
2
2
 
3
- Command-line interface for syncing system prompts from SysPromptHub to your projects.
3
+ Uplevel your AI development assistant with guardrails from [SysPromptHub](https://sysprompthub.com).
4
4
 
5
- For programmatic usage, see [@sysprompthub/sdk](https://www.npmjs.com/package/@sysprompthub/sdk).
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,40 +14,109 @@ For programmatic usage, see [@sysprompthub/sdk](https://www.npmjs.com/package/@s
10
14
  npm install -g @sysprompthub/cli
11
15
  ```
12
16
 
13
- ## CLI Usage
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
23
+
24
+ ## Commands
14
25
 
15
- ### Init Command
26
+ ### Initial setup
16
27
 
17
- Initialize SysPromptHub configuration for your project:
28
+ Initialize or update your development environment.
18
29
 
19
30
  ```bash
20
31
  sysprompthub init
21
32
  ```
22
33
 
23
- This interactive command will:
24
- 1. Prompt for your API key (find yours at https://app.sysprompthub.com/api-keys)
25
- 2. Let you search and select a prompt pack
26
- 3. Let you select your AI assistants (Copilot, Claude, or custom path)
27
- 4. Save workspace configuration to `.sysprompthub.json`
34
+ ### Configure prompt packs for your assistant
28
35
 
29
- ### Sync Command
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
+ ```
30
40
 
31
- Sync prompts from your configured pack:
32
41
 
33
- ```bash
42
+ ### Sync your prompt packs
43
+
44
+ Download and install configured prompts.
45
+
46
+ ```shell
34
47
  sysprompthub sync
35
48
  ```
36
49
 
37
- ## Workflow
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.
64
+
65
+ **Example:**
66
+ ```bash
67
+ # Set main prompt
68
+ sysprompthub copilot set owner/general/latest
69
+
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"
73
+ ```
74
+
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
87
+ ```
88
+
89
+ ### Custom Paths
90
+
91
+ See `sysprompthub custom --help` for more details.
38
92
 
39
- 1. **First time setup**: Run `sysprompthub init` to configure your API key and project
40
- 2. **Sync prompts**: Run `sysprompthub sync` to download and install prompts
41
- 3. **Update**: Run `sysprompthub init` again to change configuration
93
+ **Example:**
94
+ ```bash
95
+ sysprompthub custom add docs/ai-prompt.md someone/markdown/latest
96
+ sysprompthub custom remove docs/ai-prompt.md
97
+ ```
98
+
99
+ ## Configuration
100
+
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
+ ```
42
115
 
43
116
  ## Contributing
44
117
 
45
- See [CONTRIBUTING.md](./CONTRIBUTING.md) for development instructions.
118
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup and guidelines.
46
119
 
47
120
  ## License
48
121
 
49
- See [LICENSE](./LICENSE)
122
+ ISC
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import { Option } from "commander";
2
2
  export class BaseCommand {
3
3
  static envOption = new Option('-e, --environment [env]')
4
+ .hideHelp()
4
5
  .choices(['production', 'development', 'local'])
5
6
  .env('SYSPROMPTHUB_ENVIRONMENT');
6
7
  }
@@ -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,8 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ // Simple integration test placeholder
3
+ // Full integration tests would require more setup
4
+ describe('CLI Integration', () => {
5
+ it('placeholder test', () => {
6
+ expect(true).toBe(true);
7
+ });
8
+ });
@@ -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
+ }