@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
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { ShowCommand } from './show.js';
|
|
3
|
+
import { mkdir, writeFile, rm } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { tmpdir } from 'os';
|
|
6
|
+
describe('ShowCommand', () => {
|
|
7
|
+
let tempDir;
|
|
8
|
+
let consoleLogSpy;
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
tempDir = join(tmpdir(), `sysprompthub-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
11
|
+
await mkdir(tempDir, { recursive: true });
|
|
12
|
+
process.chdir(tempDir);
|
|
13
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
17
|
+
consoleLogSpy.mockRestore();
|
|
18
|
+
});
|
|
19
|
+
describe('empty config', () => {
|
|
20
|
+
it('should display help message when no packs configured', async () => {
|
|
21
|
+
const cmd = new ShowCommand();
|
|
22
|
+
await cmd.run();
|
|
23
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('No packs configured for this project.');
|
|
24
|
+
expect(consoleLogSpy).toHaveBeenCalledWith('\nGet started:');
|
|
25
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(' • sysprompthub copilot set <pack>');
|
|
26
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(' • sysprompthub claude set <pack>');
|
|
27
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(' • sysprompthub custom add <path> <pack>');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('assistant packs', () => {
|
|
31
|
+
it('should display copilot root pack', async () => {
|
|
32
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
33
|
+
packs: [
|
|
34
|
+
{ type: 'copilot', pack: 'user/copilot-pack/latest' }
|
|
35
|
+
]
|
|
36
|
+
}));
|
|
37
|
+
const cmd = new ShowCommand();
|
|
38
|
+
await cmd.run();
|
|
39
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
40
|
+
expect(output).toContain('copilot');
|
|
41
|
+
expect(output).toContain('pack:');
|
|
42
|
+
expect(output).toContain('user/copilot-pack/latest');
|
|
43
|
+
expect(output).toContain('prompt:');
|
|
44
|
+
expect(output).toContain('.github/copilot-instructions.md');
|
|
45
|
+
});
|
|
46
|
+
it('should display claude root pack', async () => {
|
|
47
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
48
|
+
packs: [
|
|
49
|
+
{ type: 'claude', pack: 'user/claude-pack/latest' }
|
|
50
|
+
]
|
|
51
|
+
}));
|
|
52
|
+
const cmd = new ShowCommand();
|
|
53
|
+
await cmd.run();
|
|
54
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
55
|
+
expect(output).toContain('claude');
|
|
56
|
+
expect(output).toContain('pack:');
|
|
57
|
+
expect(output).toContain('user/claude-pack/latest');
|
|
58
|
+
expect(output).toContain('prompt:');
|
|
59
|
+
expect(output).toContain('.claude/system.md');
|
|
60
|
+
});
|
|
61
|
+
it('should display copilot agent with frontmatter', async () => {
|
|
62
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
63
|
+
packs: [
|
|
64
|
+
{
|
|
65
|
+
type: 'copilot',
|
|
66
|
+
pack: 'user/python/latest',
|
|
67
|
+
agent: {
|
|
68
|
+
name: 'python',
|
|
69
|
+
description: 'Python expert',
|
|
70
|
+
model: 'gpt-4'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}));
|
|
75
|
+
const cmd = new ShowCommand();
|
|
76
|
+
await cmd.run();
|
|
77
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
78
|
+
expect(output).toContain('copilot agent "python"');
|
|
79
|
+
expect(output).toContain('pack:');
|
|
80
|
+
expect(output).toContain('user/python/latest');
|
|
81
|
+
expect(output).toContain('prompt:');
|
|
82
|
+
expect(output).toContain('.github/agents/python.agent.md');
|
|
83
|
+
expect(output).toContain('description:');
|
|
84
|
+
expect(output).toContain('Python expert');
|
|
85
|
+
expect(output).toContain('model:');
|
|
86
|
+
expect(output).toContain('gpt-4');
|
|
87
|
+
});
|
|
88
|
+
it('should display claude skill with frontmatter', async () => {
|
|
89
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
90
|
+
packs: [
|
|
91
|
+
{
|
|
92
|
+
type: 'claude',
|
|
93
|
+
pack: 'user/typescript/latest',
|
|
94
|
+
agent: {
|
|
95
|
+
name: 'typescript',
|
|
96
|
+
description: 'TypeScript expert'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}));
|
|
101
|
+
const cmd = new ShowCommand();
|
|
102
|
+
await cmd.run();
|
|
103
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
104
|
+
expect(output).toContain('claude skill "typescript"');
|
|
105
|
+
expect(output).toContain('pack:');
|
|
106
|
+
expect(output).toContain('user/typescript/latest');
|
|
107
|
+
expect(output).toContain('prompt:');
|
|
108
|
+
expect(output).toContain('.claude/skills/typescript/SKILL.md');
|
|
109
|
+
expect(output).toContain('description:');
|
|
110
|
+
expect(output).toContain('TypeScript expert');
|
|
111
|
+
});
|
|
112
|
+
it('should display environment when present', async () => {
|
|
113
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
114
|
+
packs: [
|
|
115
|
+
{
|
|
116
|
+
type: 'copilot',
|
|
117
|
+
pack: 'user/pack/latest',
|
|
118
|
+
environment: 'development'
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}));
|
|
122
|
+
const cmd = new ShowCommand();
|
|
123
|
+
await cmd.run();
|
|
124
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
125
|
+
expect(output).toContain('environment:');
|
|
126
|
+
expect(output).toContain('development');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe('sorting', () => {
|
|
130
|
+
it('should sort assistant packs by type then agent name', async () => {
|
|
131
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
132
|
+
packs: [
|
|
133
|
+
{ type: 'copilot', pack: 'user/root/latest' },
|
|
134
|
+
{ type: 'claude', pack: 'user/typescript/latest', agent: { name: 'typescript' } },
|
|
135
|
+
{ type: 'copilot', pack: 'user/python/latest', agent: { name: 'python' } },
|
|
136
|
+
{ type: 'claude', pack: 'user/root/latest' },
|
|
137
|
+
{ type: 'copilot', pack: 'user/java/latest', agent: { name: 'java' } }
|
|
138
|
+
]
|
|
139
|
+
}));
|
|
140
|
+
const cmd = new ShowCommand();
|
|
141
|
+
await cmd.run();
|
|
142
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
143
|
+
// Check order: claude root, claude typescript, copilot root, copilot java, copilot python
|
|
144
|
+
const claudeRootPos = output.indexOf('claude\n');
|
|
145
|
+
const claudeSkillPos = output.indexOf('claude skill "typescript"');
|
|
146
|
+
const copilotRootPos = output.indexOf('copilot\n');
|
|
147
|
+
const copilotJavaPos = output.indexOf('copilot agent "java"');
|
|
148
|
+
const copilotPythonPos = output.indexOf('copilot agent "python"');
|
|
149
|
+
expect(claudeRootPos).toBeLessThan(claudeSkillPos);
|
|
150
|
+
expect(claudeSkillPos).toBeLessThan(copilotRootPos);
|
|
151
|
+
expect(copilotRootPos).toBeLessThan(copilotJavaPos);
|
|
152
|
+
expect(copilotJavaPos).toBeLessThan(copilotPythonPos);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('custom packs', () => {
|
|
156
|
+
it('should display custom pack with path', async () => {
|
|
157
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
158
|
+
packs: [
|
|
159
|
+
{
|
|
160
|
+
type: 'custom',
|
|
161
|
+
pack: 'user/docs/latest',
|
|
162
|
+
path: 'docs/prompt.md'
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}));
|
|
166
|
+
const cmd = new ShowCommand();
|
|
167
|
+
await cmd.run();
|
|
168
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
169
|
+
expect(output).toContain('custom "docs/prompt.md"');
|
|
170
|
+
expect(output).toContain('user/docs/latest');
|
|
171
|
+
});
|
|
172
|
+
it('should display custom pack environment', async () => {
|
|
173
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
174
|
+
packs: [
|
|
175
|
+
{
|
|
176
|
+
type: 'custom',
|
|
177
|
+
pack: 'user/docs/latest',
|
|
178
|
+
path: 'docs/prompt.md',
|
|
179
|
+
environment: 'development'
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}));
|
|
183
|
+
const cmd = new ShowCommand();
|
|
184
|
+
await cmd.run();
|
|
185
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
186
|
+
expect(output).toContain('[development]');
|
|
187
|
+
});
|
|
188
|
+
it('should sort custom packs by path', async () => {
|
|
189
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
190
|
+
packs: [
|
|
191
|
+
{ type: 'custom', pack: 'user/pack3/latest', path: 'z-path.md' },
|
|
192
|
+
{ type: 'custom', pack: 'user/pack1/latest', path: 'a-path.md' },
|
|
193
|
+
{ type: 'custom', pack: 'user/pack2/latest', path: 'm-path.md' }
|
|
194
|
+
]
|
|
195
|
+
}));
|
|
196
|
+
const cmd = new ShowCommand();
|
|
197
|
+
await cmd.run();
|
|
198
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
199
|
+
const aPathPos = output.indexOf('custom "a-path.md"');
|
|
200
|
+
const mPathPos = output.indexOf('custom "m-path.md"');
|
|
201
|
+
const zPathPos = output.indexOf('custom "z-path.md"');
|
|
202
|
+
expect(aPathPos).toBeLessThan(mPathPos);
|
|
203
|
+
expect(mPathPos).toBeLessThan(zPathPos);
|
|
204
|
+
});
|
|
205
|
+
it('should display custom packs after assistant packs', async () => {
|
|
206
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
207
|
+
packs: [
|
|
208
|
+
{ type: 'custom', pack: 'user/custom/latest', path: 'docs/prompt.md' },
|
|
209
|
+
{ type: 'copilot', pack: 'user/copilot/latest' }
|
|
210
|
+
]
|
|
211
|
+
}));
|
|
212
|
+
const cmd = new ShowCommand();
|
|
213
|
+
await cmd.run();
|
|
214
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
215
|
+
const copilotPos = output.indexOf('copilot');
|
|
216
|
+
const customPos = output.indexOf('custom "docs/prompt.md"');
|
|
217
|
+
expect(copilotPos).toBeLessThan(customPos);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
describe('mixed configurations', () => {
|
|
221
|
+
it('should display all pack types correctly', async () => {
|
|
222
|
+
await writeFile(join(tempDir, '.sysprompthub.json'), JSON.stringify({
|
|
223
|
+
packs: [
|
|
224
|
+
{ type: 'copilot', pack: 'user/copilot-root/latest' },
|
|
225
|
+
{ type: 'copilot', pack: 'user/python/latest', agent: { name: 'python', description: 'Python expert' } },
|
|
226
|
+
{ type: 'claude', pack: 'user/claude-root/latest' },
|
|
227
|
+
{ type: 'claude', pack: 'user/typescript/latest', agent: { name: 'typescript' } },
|
|
228
|
+
{ type: 'custom', pack: 'user/docs/latest', path: 'docs/prompt.md' }
|
|
229
|
+
]
|
|
230
|
+
}));
|
|
231
|
+
const cmd = new ShowCommand();
|
|
232
|
+
await cmd.run();
|
|
233
|
+
const output = consoleLogSpy.mock.calls.map((call) => call[0]).join('\n');
|
|
234
|
+
expect(output).toContain('claude');
|
|
235
|
+
expect(output).toContain('claude skill "typescript"');
|
|
236
|
+
expect(output).toContain('copilot');
|
|
237
|
+
expect(output).toContain('copilot agent "python"');
|
|
238
|
+
expect(output).toContain('custom "docs/prompt.md"');
|
|
239
|
+
expect(output).toContain('Python expert');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
package/dist/commands/sync.js
CHANGED
|
@@ -3,31 +3,33 @@ import { DefaultConfigManager, NodeFileSystem, SyncManager, UserConfigManager }
|
|
|
3
3
|
import { BaseCommand } from "./base-command.js";
|
|
4
4
|
export class SyncCommand extends BaseCommand {
|
|
5
5
|
fs = new NodeFileSystem();
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
configManager = new DefaultConfigManager();
|
|
7
|
+
userConfigManager = new UserConfigManager();
|
|
8
8
|
async run(_options) {
|
|
9
9
|
// Load user-level config (API key)
|
|
10
|
-
const userCfg = await this.
|
|
10
|
+
const userCfg = await this.userConfigManager.load(this.fs);
|
|
11
11
|
if (!userCfg?.apiKey) {
|
|
12
12
|
console.error('Error: API key not configured. Run "sysprompthub init" to configure.');
|
|
13
13
|
process.exit(1);
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
// Load
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
-
console.error('Error: No
|
|
16
|
+
// Load project config (with automatic migration)
|
|
17
|
+
const projectCfg = await this.configManager.load(this.fs);
|
|
18
|
+
if (!projectCfg) {
|
|
19
|
+
console.error('Error: No project configuration found.');
|
|
20
|
+
console.error('Use "sysprompthub copilot set <pack>" or "sysprompthub claude set <pack>" to configure.');
|
|
20
21
|
process.exit(1);
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
if (!
|
|
24
|
-
console.error('Error:
|
|
24
|
+
if (!projectCfg.packs || projectCfg.packs.length === 0) {
|
|
25
|
+
console.error('Error: No packs configured.');
|
|
26
|
+
console.error('Use "sysprompthub copilot set <pack>" or "sysprompthub claude set <pack>" to configure.');
|
|
25
27
|
process.exit(1);
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
// Combine configs
|
|
29
31
|
const config = {
|
|
30
|
-
...
|
|
32
|
+
packs: [...projectCfg.packs],
|
|
31
33
|
apiKey: userCfg.apiKey
|
|
32
34
|
};
|
|
33
35
|
const syncManager = new SyncManager();
|
|
@@ -6,9 +6,10 @@ vi.mock('@sysprompthub/sdk', () => {
|
|
|
6
6
|
writeFile: vi.fn(),
|
|
7
7
|
exists: vi.fn()
|
|
8
8
|
};
|
|
9
|
-
const
|
|
9
|
+
const mockConfigManager = {
|
|
10
10
|
load: vi.fn(),
|
|
11
|
-
update: vi.fn()
|
|
11
|
+
update: vi.fn(),
|
|
12
|
+
save: vi.fn()
|
|
12
13
|
};
|
|
13
14
|
const mockUserConfig = {
|
|
14
15
|
load: vi.fn(),
|
|
@@ -27,17 +28,17 @@ vi.mock('@sysprompthub/sdk', () => {
|
|
|
27
28
|
exists = mockFs.exists;
|
|
28
29
|
},
|
|
29
30
|
DefaultConfigManager: class {
|
|
30
|
-
load =
|
|
31
|
-
update =
|
|
31
|
+
load = mockConfigManager.load;
|
|
32
|
+
update = mockConfigManager.update;
|
|
33
|
+
save = mockConfigManager.save;
|
|
32
34
|
},
|
|
33
35
|
UserConfigManager: class {
|
|
34
36
|
load = mockUserConfig.load;
|
|
35
37
|
save = mockUserConfig.save;
|
|
36
38
|
},
|
|
37
|
-
// Export mocks so tests can access them
|
|
38
39
|
__mocks: {
|
|
39
40
|
mockFs,
|
|
40
|
-
|
|
41
|
+
mockConfigManager,
|
|
41
42
|
mockUserConfig,
|
|
42
43
|
mockSyncManager
|
|
43
44
|
}
|
|
@@ -56,11 +57,13 @@ describe('SyncCommand', () => {
|
|
|
56
57
|
mocks.mockFs.readFile.mockReset();
|
|
57
58
|
mocks.mockFs.writeFile.mockReset();
|
|
58
59
|
mocks.mockFs.exists.mockReset();
|
|
59
|
-
mocks.
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
mocks.mockConfigManager.load.mockReset().mockResolvedValue({
|
|
61
|
+
packs: [
|
|
62
|
+
{ type: 'copilot', pack: 'owner/pack/latest' }
|
|
63
|
+
]
|
|
62
64
|
});
|
|
63
|
-
mocks.
|
|
65
|
+
mocks.mockConfigManager.update.mockReset();
|
|
66
|
+
mocks.mockConfigManager.save.mockReset();
|
|
64
67
|
mocks.mockUserConfig.load.mockReset().mockResolvedValue({
|
|
65
68
|
apiKey: 'a'.repeat(40)
|
|
66
69
|
});
|
|
@@ -87,10 +90,12 @@ describe('SyncCommand', () => {
|
|
|
87
90
|
it('should sync with valid configs', async () => {
|
|
88
91
|
await syncCommand.run({});
|
|
89
92
|
expect(mocks.mockUserConfig.load).toHaveBeenCalled();
|
|
90
|
-
expect(mocks.
|
|
93
|
+
expect(mocks.mockConfigManager.load).toHaveBeenCalled();
|
|
91
94
|
expect(mocks.mockSyncManager.sync).toHaveBeenCalledWith({
|
|
92
95
|
config: expect.objectContaining({
|
|
93
|
-
|
|
96
|
+
packs: expect.arrayContaining([
|
|
97
|
+
expect.objectContaining({ type: 'copilot', pack: 'owner/pack/latest' })
|
|
98
|
+
]),
|
|
94
99
|
apiKey: 'a'.repeat(40)
|
|
95
100
|
}),
|
|
96
101
|
fs: expect.any(Object)
|
|
@@ -103,29 +108,34 @@ describe('SyncCommand', () => {
|
|
|
103
108
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: API key not configured. Run "sysprompthub init" to configure.');
|
|
104
109
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
105
110
|
});
|
|
106
|
-
it('should exit with error if no
|
|
107
|
-
mocks.
|
|
111
|
+
it('should exit with error if no project configuration found', async () => {
|
|
112
|
+
mocks.mockConfigManager.load.mockResolvedValue(null);
|
|
108
113
|
await syncCommand.run({});
|
|
109
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: No
|
|
114
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: No project configuration found.');
|
|
110
115
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
111
116
|
});
|
|
112
|
-
it('should exit with error if
|
|
113
|
-
mocks.
|
|
114
|
-
assistants: ['copilot']
|
|
115
|
-
});
|
|
117
|
+
it('should exit with error if no packs configured', async () => {
|
|
118
|
+
mocks.mockConfigManager.load.mockResolvedValue({ packs: [] });
|
|
116
119
|
await syncCommand.run({});
|
|
117
|
-
expect(consoleErrorSpy).toHaveBeenCalledWith('Error:
|
|
120
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: No packs configured.');
|
|
118
121
|
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
119
122
|
});
|
|
120
|
-
it('should
|
|
121
|
-
mocks.
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
it('should sync multiple packs', async () => {
|
|
124
|
+
mocks.mockConfigManager.load.mockResolvedValue({
|
|
125
|
+
packs: [
|
|
126
|
+
{ type: 'copilot', pack: 'owner/pack1/latest' },
|
|
127
|
+
{ type: 'claude', pack: 'owner/pack2/latest', agent: { name: 'test' } },
|
|
128
|
+
{ type: 'custom', pack: 'owner/pack3/latest', path: 'custom/path' }
|
|
129
|
+
]
|
|
124
130
|
});
|
|
125
131
|
await syncCommand.run({});
|
|
126
132
|
expect(mocks.mockSyncManager.sync).toHaveBeenCalledWith({
|
|
127
133
|
config: expect.objectContaining({
|
|
128
|
-
|
|
134
|
+
packs: expect.arrayContaining([
|
|
135
|
+
expect.objectContaining({ type: 'copilot' }),
|
|
136
|
+
expect.objectContaining({ type: 'claude', agent: { name: 'test' } }),
|
|
137
|
+
expect.objectContaining({ type: 'custom', path: 'custom/path' })
|
|
138
|
+
])
|
|
129
139
|
}),
|
|
130
140
|
fs: expect.any(Object)
|
|
131
141
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { NodeFileSystem, DefaultConfigManager } from '@sysprompthub/sdk';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Loads project config editor from .sysprompthub.json.
|
|
5
|
+
* Returns an editor with empty config if file doesn't exist.
|
|
6
|
+
*/
|
|
7
|
+
export async function loadProjectConfigEditor() {
|
|
8
|
+
const fs = new NodeFileSystem();
|
|
9
|
+
const manager = new DefaultConfigManager();
|
|
10
|
+
return await manager.load(fs);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validates pack name format: owner/pack/version
|
|
14
|
+
*/
|
|
15
|
+
export function validatePackName(pack) {
|
|
16
|
+
const packRegex = /^[a-z0-9_-]+\/[a-z0-9_-]+\/[a-z0-9_.-]+$/i;
|
|
17
|
+
return packRegex.test(pack);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Validates agent/skill name format: lowercase alphanumeric, hyphens, underscores
|
|
21
|
+
*/
|
|
22
|
+
export function validateAgentName(name) {
|
|
23
|
+
const nameRegex = /^[a-z0-9_-]+$/;
|
|
24
|
+
return nameRegex.test(name);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validates path is relative (not absolute)
|
|
28
|
+
*/
|
|
29
|
+
export function validateRelativePath(path) {
|
|
30
|
+
return !resolve(path).startsWith('/') || resolve(path).startsWith(process.cwd());
|
|
31
|
+
}
|
|
32
|
+
export function parseFrontmatterArg(value, previous) {
|
|
33
|
+
const keyValue = value.split('=', 2);
|
|
34
|
+
return { ...previous, [keyValue[0]]: keyValue[1] };
|
|
35
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import { InitCommand } from './commands/init.js';
|
|
4
4
|
import { SyncCommand } from './commands/sync.js';
|
|
5
|
+
import { ShowCommand } from './commands/show.js';
|
|
6
|
+
import { createCopilotCommand } from './commands/copilot.js';
|
|
7
|
+
import { createClaudeCommand } from './commands/claude.js';
|
|
8
|
+
import { createCustomCommand } from './commands/custom.js';
|
|
9
|
+
import { VERSION } from './version.js';
|
|
5
10
|
const program = new Command();
|
|
6
11
|
program
|
|
7
12
|
.name('sysprompthub')
|
|
8
13
|
.description('SysPromptHub CLI - Manage system prompts for AI assistants')
|
|
9
|
-
.version(
|
|
14
|
+
.version(VERSION);
|
|
10
15
|
const initCmd = new InitCommand();
|
|
11
16
|
const syncCmd = new SyncCommand();
|
|
17
|
+
const showCmd = new ShowCommand();
|
|
12
18
|
program.addCommand(initCmd.create());
|
|
13
19
|
program.addCommand(syncCmd.create());
|
|
20
|
+
program.addCommand(showCmd.create());
|
|
21
|
+
program.addCommand(createCopilotCommand());
|
|
22
|
+
program.addCommand(createClaudeCommand());
|
|
23
|
+
program.addCommand(createCustomCommand());
|
|
14
24
|
program.parse();
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "2.0.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sysprompthub/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI for syncing system prompts from SysPromptHub",
|
|
5
5
|
"authorEmail": "info@sysprompthub.com",
|
|
6
6
|
"authorName": "SysPromptHub",
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"cli",
|
|
23
23
|
"sysprompthub"
|
|
24
24
|
],
|
|
25
|
-
"author":
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "SysPromptHub",
|
|
27
|
+
"email": "info@susprompthub.com"
|
|
28
|
+
},
|
|
26
29
|
"license": "ISC",
|
|
27
30
|
"engines": {
|
|
28
31
|
"node": ">=18.0.0",
|
|
@@ -30,27 +33,30 @@
|
|
|
30
33
|
},
|
|
31
34
|
"dependencies": {
|
|
32
35
|
"@inquirer/prompts": "^8.1.0",
|
|
33
|
-
"@sysprompthub/sdk": "^
|
|
34
|
-
"commander": "^14.0.
|
|
36
|
+
"@sysprompthub/sdk": "^2.0.0",
|
|
37
|
+
"commander": "^14.0.3",
|
|
35
38
|
"is-valid-path": "^0.1.1",
|
|
36
39
|
"open": "^11.0.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@types/is-valid-path": "^0.1.2",
|
|
40
43
|
"@types/node": "^25.0.3",
|
|
41
|
-
"@vitest/coverage-v8": "^4.0.
|
|
42
|
-
"@vitest/ui": "^4.0.
|
|
44
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
45
|
+
"@vitest/ui": "^4.0.18",
|
|
43
46
|
"tsx": "^4.21.0",
|
|
44
47
|
"typescript": "^5.9.3",
|
|
45
|
-
"vitest": "^4.0.
|
|
48
|
+
"vitest": "^4.0.18"
|
|
46
49
|
},
|
|
47
50
|
"scripts": {
|
|
51
|
+
"build:version": "echo \"export const VERSION=\\\"$npm_package_version\\\"\" > src/version.ts",
|
|
52
|
+
"prebuild": "pnpm run build:version",
|
|
48
53
|
"build": "tsc",
|
|
49
54
|
"dev": "tsx src/index.ts",
|
|
50
55
|
"typecheck": "tsc --noEmit",
|
|
51
56
|
"test": "vitest run",
|
|
52
57
|
"test:watch": "vitest",
|
|
53
58
|
"test:ui": "vitest --ui",
|
|
54
|
-
"test:coverage": "vitest run --coverage"
|
|
59
|
+
"test:coverage": "vitest run --coverage",
|
|
60
|
+
"tag": "git tag --force cli-v${npm_package_version} && git push --tags --force"
|
|
55
61
|
}
|
|
56
62
|
}
|