@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.
@@ -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
+ });
@@ -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
- workspaceConfig = new DefaultConfigManager();
7
- userConfig = new UserConfigManager();
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.userConfig.load(this.fs);
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 workspace-level config (pack name, assistants, path)
17
- const workspaceCfg = await this.workspaceConfig.load(this.fs);
18
- if (!workspaceCfg) {
19
- console.error('Error: No workspace configuration found. Run "sysprompthub init" first.');
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 (!workspaceCfg.packName) {
24
- console.error('Error: Pack name not configured. Run "sysprompthub init" to configure.');
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
- ...workspaceCfg,
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 mockWorkspaceConfig = {
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 = mockWorkspaceConfig.load;
31
- update = mockWorkspaceConfig.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
- mockWorkspaceConfig,
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.mockWorkspaceConfig.load.mockReset().mockResolvedValue({
60
- packName: 'user/pack/latest',
61
- assistants: ['copilot']
60
+ mocks.mockConfigManager.load.mockReset().mockResolvedValue({
61
+ packs: [
62
+ { type: 'copilot', pack: 'owner/pack/latest' }
63
+ ]
62
64
  });
63
- mocks.mockWorkspaceConfig.update.mockReset();
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.mockWorkspaceConfig.load).toHaveBeenCalled();
93
+ expect(mocks.mockConfigManager.load).toHaveBeenCalled();
91
94
  expect(mocks.mockSyncManager.sync).toHaveBeenCalledWith({
92
95
  config: expect.objectContaining({
93
- packName: 'user/pack/latest',
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 workspace configuration found', async () => {
107
- mocks.mockWorkspaceConfig.load.mockResolvedValue(null);
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 workspace configuration found. Run "sysprompthub init" first.');
114
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error: No project configuration found.');
110
115
  expect(processExitSpy).toHaveBeenCalledWith(1);
111
116
  });
112
- it('should exit with error if pack name not configured', async () => {
113
- mocks.mockWorkspaceConfig.load.mockResolvedValue({
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: Pack name not configured. Run "sysprompthub init" to configure.');
120
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error: No packs configured.');
118
121
  expect(processExitSpy).toHaveBeenCalledWith(1);
119
122
  });
120
- it('should use workspace environment when option not provided', async () => {
121
- mocks.mockWorkspaceConfig.load.mockResolvedValue({
122
- packName: 'user/pack/latest',
123
- environment: 'staging'
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
- environment: 'staging'
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('1.0.0');
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();
@@ -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": "1.1.1",
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": "^1.1.0",
34
- "commander": "^14.0.2",
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.16",
42
- "@vitest/ui": "^4.0.16",
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.16"
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
  }