@sysprompthub/cli 1.0.0 → 1.1.1

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Command-line interface for syncing system prompts from SysPromptHub to your projects.
4
4
 
5
- For programmatic usage, see [@sysprompthub/sdk](../nodejs_sdk).
5
+ For programmatic usage, see [@sysprompthub/sdk](https://www.npmjs.com/package/@sysprompthub/sdk).
6
6
 
7
7
  ## Installation
8
8
 
@@ -21,7 +21,7 @@ sysprompthub init
21
21
  ```
22
22
 
23
23
  This interactive command will:
24
- 1. Prompt for your API key (stored in `~/.sysprompthub/config.json`)
24
+ 1. Prompt for your API key (find yours at https://app.sysprompthub.com/api-keys)
25
25
  2. Let you search and select a prompt pack
26
26
  3. Let you select your AI assistants (Copilot, Claude, or custom path)
27
27
  4. Save workspace configuration to `.sysprompthub.json`
@@ -34,37 +34,6 @@ Sync prompts from your configured pack:
34
34
  sysprompthub sync
35
35
  ```
36
36
 
37
- Options:
38
- - `-e, --environment <ENV>` - API environment to use (local, development, staging, production)
39
-
40
- ### Configuration
41
-
42
- #### User-level Config (`~/.sysprompthub/config.json`)
43
- Stores your API key:
44
- ```json
45
- {
46
- "apiKey": "your-40-character-api-key"
47
- }
48
- ```
49
-
50
- #### Workspace-level Config (`.sysprompthub.json`)
51
- Stores project-specific settings:
52
- ```json
53
- {
54
- "packName": "owner/pack/latest",
55
- "assistants": ["copilot", "claude"],
56
- "environment": "production"
57
- }
58
- ```
59
-
60
- You can also use a custom path instead of assistants:
61
- ```json
62
- {
63
- "packName": "owner/pack/latest",
64
- "path": "prompts/system-prompt.md"
65
- }
66
- ```
67
-
68
37
  ## Workflow
69
38
 
70
39
  1. **First time setup**: Run `sysprompthub init` to configure your API key and project
@@ -77,4 +46,4 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md) for development instructions.
77
46
 
78
47
  ## License
79
48
 
80
- ISC
49
+ See [LICENSE](./LICENSE)
@@ -21,7 +21,6 @@ export class InitCommand extends BaseCommand {
21
21
  let workspaceConfig = { ...existingWorkspaceConfig };
22
22
  if (environment)
23
23
  workspaceConfig.environment = environment;
24
- // console.info('env', environment, 'ws', workspaceConfig);
25
24
  // Step 2: Pack Name (workspace-level)
26
25
  const client = new SysPromptHubClient(apiKey, workspaceConfig.environment);
27
26
  workspaceConfig.packName = await this.promptForPackName(client, workspaceConfig.packName);
@@ -35,7 +34,20 @@ export class InitCommand extends BaseCommand {
35
34
  await this.workspaceConfigManager.update(this.fs, workspaceConfig);
36
35
  console.log('\n✓ User config saved to ~/.sysprompthub/config.json');
37
36
  console.log('✓ Project config saved to .sysprompthub.json');
38
- console.log('Run "sysprompthub sync" to download your prompts.\n');
37
+ // Ask if user wants to sync now
38
+ const shouldSync = await confirm({
39
+ message: 'Would you like to sync your prompts now?',
40
+ default: true
41
+ });
42
+ if (shouldSync) {
43
+ console.log('\nSyncing prompts...');
44
+ const { SyncCommand } = await import('./sync.js');
45
+ const syncCommand = new SyncCommand();
46
+ await syncCommand.run({});
47
+ }
48
+ else {
49
+ console.log('Run "sysprompthub sync" to download your prompts.\n');
50
+ }
39
51
  }
40
52
  async promptForApiKey(existing) {
41
53
  const apiKey = await input({
@@ -77,64 +89,78 @@ export class InitCommand extends BaseCommand {
77
89
  return existing;
78
90
  }
79
91
  }
80
- const searchResults = await search({
81
- message: 'Select a pack:',
92
+ const packInfo = await this.searchAndSelectPack(client);
93
+ const version = await this.selectPackVersion(packInfo.name, packInfo.latestVersion);
94
+ return `${packInfo.name}/${version}`;
95
+ }
96
+ async searchAndSelectPack(client) {
97
+ const packResult = await search({
98
+ message: 'Type to search for packs...',
82
99
  source: async (term) => {
83
- if (!term || term.length < 2) {
100
+ if (!term || term.length < 3) {
84
101
  return [];
85
102
  }
86
103
  try {
87
104
  const results = await client.searchPacks(term);
88
- const choices = [];
89
- for (const result of results) {
90
- console.info(`Found pack "${JSON.stringify(result)}"`);
91
- const baseDescription = `Current version: ${result.version}`;
92
- // Option 1: /latest
93
- choices.push({
94
- name: `${result.name}/latest`,
95
- value: `${result.name}/latest`,
96
- description: `${baseDescription} (always use latest)`
97
- });
98
- // Option 2: /current_version
99
- choices.push({
100
- name: `${result.name}/${result.version}`,
101
- value: `${result.name}/${result.version}`,
102
- description: `${baseDescription} (pin to current)`
103
- });
104
- // Option 3: Custom version
105
- choices.push({
106
- name: `${result.name}/[custom version]`,
107
- value: `custom:${result.name}:${result.version}`,
108
- description: `${baseDescription} (enter specific version)`
109
- });
110
- }
111
- return choices;
105
+ return results.map(result => ({
106
+ name: result.name,
107
+ value: JSON.stringify({ name: result.name, latestVersion: result.version }),
108
+ description: `Current version: ${result.version}`
109
+ }));
112
110
  }
113
111
  catch (error) {
114
- console.error('Error searching for packs:', error);
115
112
  return [];
116
113
  }
117
114
  }
118
115
  });
119
- // Handle custom version selection
120
- if (searchResults.startsWith('custom:')) {
121
- const [, fullName, maxVersionStr] = searchResults.split(':');
122
- const maxVersion = parseInt(maxVersionStr, 10);
123
- const customVersion = await input({
124
- message: `Enter version number for ${fullName} (1-${maxVersion}):`,
125
- validate: (value) => {
126
- if (!value) {
127
- return 'Version is required';
128
- }
129
- if (!SysPromptHubClient.validateVersion(value, maxVersion)) {
130
- return `Version must be between 1 and ${maxVersion}`;
116
+ return JSON.parse(packResult);
117
+ }
118
+ async selectPackVersion(packName, latestVersion) {
119
+ const versionChoice = await search({
120
+ message: `Select version for ${packName}:`,
121
+ source: async () => {
122
+ const choices = [
123
+ {
124
+ name: 'latest',
125
+ value: 'latest',
126
+ description: 'Always use the latest version'
127
+ },
128
+ {
129
+ name: `current (v${latestVersion})`,
130
+ value: String(latestVersion),
131
+ description: 'Use current version and manually update'
131
132
  }
132
- return true;
133
+ ];
134
+ if (latestVersion > 1) {
135
+ choices.push({
136
+ name: '(other)',
137
+ value: 'custom',
138
+ description: 'Choose an older version'
139
+ });
133
140
  }
134
- });
135
- return `${fullName}/${customVersion}`;
141
+ return choices;
142
+ }
143
+ });
144
+ if (versionChoice === 'custom') {
145
+ return await this.promptForCustomVersion(packName, latestVersion);
136
146
  }
137
- return searchResults;
147
+ return versionChoice === 'latest' ? 'latest' : Number(versionChoice);
148
+ }
149
+ async promptForCustomVersion(packName, latestVersion) {
150
+ const customVersion = await input({
151
+ message: `Enter version number for ${packName} (1-${latestVersion}):`,
152
+ default: String(latestVersion),
153
+ validate: (value) => {
154
+ if (!value) {
155
+ return 'Version is required';
156
+ }
157
+ if (!SysPromptHubClient.validateVersion(value, latestVersion)) {
158
+ return `Version must be between 1 and ${latestVersion}`;
159
+ }
160
+ return true;
161
+ }
162
+ });
163
+ return Number(customVersion);
138
164
  }
139
165
  async promptForAssistants(existing) {
140
166
  const am = new AssistantManager();
@@ -114,11 +114,13 @@ describe('InitCommand', () => {
114
114
  describe('run', () => {
115
115
  it('should prompt for API key, pack name, and assistants', async () => {
116
116
  promptMocks.input.mockResolvedValue('a'.repeat(40));
117
- promptMocks.search.mockResolvedValue('owner/pack/latest');
117
+ promptMocks.search
118
+ .mockResolvedValueOnce(JSON.stringify({ name: 'owner/pack', version: 5 })) // Pack search
119
+ .mockResolvedValueOnce('latest'); // Version selection
118
120
  promptMocks.checkbox.mockResolvedValue(['copilot']);
119
121
  await initCommand.run({});
120
122
  expect(promptMocks.input).toHaveBeenCalled();
121
- expect(promptMocks.search).toHaveBeenCalled();
123
+ expect(promptMocks.search).toHaveBeenCalledTimes(2);
122
124
  expect(promptMocks.checkbox).toHaveBeenCalled();
123
125
  expect(mocks.mockUserConfig.save).toHaveBeenCalledWith(expect.any(Object), { apiKey: 'a'.repeat(40) });
124
126
  expect(mocks.mockWorkspaceConfig.update).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({
@@ -129,7 +131,9 @@ describe('InitCommand', () => {
129
131
  it('should keep existing API key if blank entered', async () => {
130
132
  mocks.mockUserConfig.load.mockResolvedValue({ apiKey: 'existing'.padEnd(40, 'x') });
131
133
  promptMocks.input.mockResolvedValueOnce(''); // API key blank
132
- promptMocks.search.mockResolvedValue('owner/pack/latest');
134
+ promptMocks.search
135
+ .mockResolvedValueOnce(JSON.stringify({ name: 'owner/pack', version: 5 })) // Pack search
136
+ .mockResolvedValueOnce('latest'); // Version selection
133
137
  promptMocks.checkbox.mockResolvedValue(['copilot']);
134
138
  await initCommand.run({});
135
139
  expect(mocks.mockUserConfig.save).toHaveBeenCalledWith(expect.any(Object), { apiKey: 'existing'.padEnd(40, 'x') });
@@ -137,7 +141,9 @@ describe('InitCommand', () => {
137
141
  it('should prompt for custom path when no assistants selected', async () => {
138
142
  promptMocks.input.mockResolvedValueOnce('a'.repeat(40)); // API key
139
143
  promptMocks.input.mockResolvedValueOnce('custom/path'); // custom path
140
- promptMocks.search.mockResolvedValue('owner/pack/latest');
144
+ promptMocks.search
145
+ .mockResolvedValueOnce(JSON.stringify({ name: 'owner/pack', version: 5 })) // Pack search
146
+ .mockResolvedValueOnce('latest'); // Version selection
141
147
  promptMocks.checkbox.mockResolvedValue([]);
142
148
  await initCommand.run({});
143
149
  expect(mocks.mockWorkspaceConfig.update).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({
@@ -147,7 +153,9 @@ describe('InitCommand', () => {
147
153
  it('should not set path when blank entered for custom path', async () => {
148
154
  promptMocks.input.mockResolvedValueOnce('a'.repeat(40)); // API key
149
155
  promptMocks.input.mockResolvedValueOnce(''); // blank custom path
150
- promptMocks.search.mockResolvedValue('owner/pack/latest');
156
+ promptMocks.search
157
+ .mockResolvedValueOnce(JSON.stringify({ name: 'owner/pack', version: 5 })) // Pack search
158
+ .mockResolvedValueOnce('latest'); // Version selection
151
159
  promptMocks.checkbox.mockResolvedValue([]);
152
160
  await initCommand.run({});
153
161
  expect(mocks.mockWorkspaceConfig.update).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({
package/package.json CHANGED
@@ -1,14 +1,13 @@
1
1
  {
2
2
  "name": "@sysprompthub/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "CLI for syncing system prompts from SysPromptHub",
5
5
  "authorEmail": "info@sysprompthub.com",
6
6
  "authorName": "SysPromptHub",
7
7
  "type": "module",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
10
- "sysprompthub": "dist/index.js",
11
- "sysprompthub-cli": "dist/index.js"
10
+ "sysprompthub": "dist/index.js"
12
11
  },
13
12
  "files": [
14
13
  "dist",
@@ -31,7 +30,7 @@
31
30
  },
32
31
  "dependencies": {
33
32
  "@inquirer/prompts": "^8.1.0",
34
- "@sysprompthub/sdk": "1.0.0",
33
+ "@sysprompthub/sdk": "^1.1.0",
35
34
  "commander": "^14.0.2",
36
35
  "is-valid-path": "^0.1.1",
37
36
  "open": "^11.0.0"