@nemus-cli/nemus 0.9.0 → 0.10.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +10 -5
  3. package/bin/workspace.js +12 -0
  4. package/dist/commands/analyze-deps.js +5 -12
  5. package/dist/commands/archive.js +13 -18
  6. package/dist/commands/branch/create.js +5 -12
  7. package/dist/commands/branch/switch.js +14 -25
  8. package/dist/commands/cache/manager.js +13 -24
  9. package/dist/commands/cleanup.js +14 -26
  10. package/dist/commands/configure-claude.js +4 -8
  11. package/dist/commands/configure.js +38 -32
  12. package/dist/commands/dashboard/session-picker.js +19 -26
  13. package/dist/commands/dashboard/workspace-picker.js +19 -26
  14. package/dist/commands/delete.js +15 -30
  15. package/dist/commands/ghq-status.js +7 -12
  16. package/dist/commands/go.js +18 -27
  17. package/dist/commands/history.js +6 -13
  18. package/dist/commands/list.js +20 -29
  19. package/dist/commands/remove-repo.js +21 -29
  20. package/dist/commands/save-context.js +5 -10
  21. package/dist/commands/sessions.js +19 -25
  22. package/dist/commands/suite/create.js +27 -53
  23. package/dist/commands/suite/delete.js +13 -25
  24. package/dist/commands/suite/export.js +20 -36
  25. package/dist/commands/suite/import.js +9 -20
  26. package/dist/commands/suite/use.js +9 -17
  27. package/dist/utils/prompt.js +26 -0
  28. package/dist/utils/prompts.js +86 -118
  29. package/package.json +3 -6
  30. package/src/commands/analyze-deps.ts +5 -9
  31. package/src/commands/archive.ts +5 -7
  32. package/src/commands/branch/create.ts +5 -9
  33. package/src/commands/branch/switch.ts +14 -22
  34. package/src/commands/cache/manager.ts +13 -21
  35. package/src/commands/cleanup.ts +14 -23
  36. package/src/commands/configure-claude.ts +4 -5
  37. package/src/commands/configure.ts +35 -29
  38. package/src/commands/dashboard/session-picker.ts +6 -11
  39. package/src/commands/dashboard/workspace-picker.ts +6 -11
  40. package/src/commands/delete.test.ts +36 -42
  41. package/src/commands/delete.ts +15 -27
  42. package/src/commands/ghq-status.ts +5 -7
  43. package/src/commands/go.ts +18 -25
  44. package/src/commands/history.ts +6 -10
  45. package/src/commands/list.test.ts +19 -26
  46. package/src/commands/list.ts +24 -31
  47. package/src/commands/remove-repo.ts +11 -17
  48. package/src/commands/save-context.ts +3 -5
  49. package/src/commands/sessions.ts +6 -10
  50. package/src/commands/suite/create.ts +28 -50
  51. package/src/commands/suite/delete.ts +14 -23
  52. package/src/commands/suite/export.ts +20 -33
  53. package/src/commands/suite/import.ts +9 -17
  54. package/src/commands/suite/use.ts +9 -14
  55. package/src/utils/prompt.ts +16 -0
  56. package/src/utils/prompts.test.ts +70 -41
  57. package/src/utils/prompts.ts +98 -128
@@ -3,12 +3,12 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
3
  const {
4
4
  mockListWorkspaces,
5
5
  mockGetWorkspaceSessions,
6
- mockPrompt,
6
+ mockSearch,
7
7
  mockWriteFile,
8
8
  } = vi.hoisted(() => ({
9
9
  mockListWorkspaces: vi.fn(),
10
10
  mockGetWorkspaceSessions: vi.fn(),
11
- mockPrompt: vi.fn(),
11
+ mockSearch: vi.fn(),
12
12
  mockWriteFile: vi.fn(),
13
13
  }));
14
14
 
@@ -33,15 +33,8 @@ vi.mock('fs/promises', () => ({
33
33
  writeFile: mockWriteFile,
34
34
  }));
35
35
 
36
- vi.mock('inquirer', () => ({
37
- default: {
38
- prompt: mockPrompt,
39
- registerPrompt: vi.fn(),
40
- },
41
- }));
42
-
43
- vi.mock('inquirer-autocomplete-prompt', () => ({
44
- default: vi.fn(),
36
+ vi.mock('../utils/prompt', () => ({
37
+ search: mockSearch,
45
38
  }));
46
39
 
47
40
  vi.mock('fuzzy', () => ({
@@ -91,11 +84,11 @@ describe('list-workspaces main', () => {
91
84
 
92
85
  it('lists workspaces and prompts for selection', async () => {
93
86
  mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('ws-a', 'ws-b'));
94
- mockPrompt.mockResolvedValueOnce({ selected: 'ws-a' });
87
+ mockSearch.mockResolvedValueOnce('ws-a');
95
88
 
96
89
  await main();
97
90
 
98
- expect(mockPrompt).toHaveBeenCalledTimes(1);
91
+ expect(mockSearch).toHaveBeenCalledTimes(1);
99
92
  expect(mockWriteFile).toHaveBeenCalledWith(
100
93
  expect.stringContaining('.workspace-last-go'),
101
94
  '/test-workspaces/ws-a',
@@ -109,7 +102,7 @@ describe('list-workspaces main', () => {
109
102
  await main();
110
103
 
111
104
  expect(logInfo).toHaveBeenCalledWith('No workspaces found');
112
- expect(mockPrompt).not.toHaveBeenCalled();
105
+ expect(mockSearch).not.toHaveBeenCalled();
113
106
  });
114
107
 
115
108
  it('does not prompt when no archived workspaces exist', async () => {
@@ -119,12 +112,12 @@ describe('list-workspaces main', () => {
119
112
  await main();
120
113
 
121
114
  expect(logInfo).toHaveBeenCalledWith('No archived workspaces found');
122
- expect(mockPrompt).not.toHaveBeenCalled();
115
+ expect(mockSearch).not.toHaveBeenCalled();
123
116
  });
124
117
 
125
118
  it('writes temp file on selection', async () => {
126
119
  mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('my-workspace'));
127
- mockPrompt.mockResolvedValueOnce({ selected: 'my-workspace' });
120
+ mockSearch.mockResolvedValueOnce('my-workspace');
128
121
 
129
122
  await main();
130
123
 
@@ -145,7 +138,7 @@ describe('list-workspaces main', () => {
145
138
  lastActiveLabel: '2m ago',
146
139
  agentType: 'pi',
147
140
  }]);
148
- mockPrompt.mockResolvedValueOnce({ selected: 'my-workspace' });
141
+ mockSearch.mockResolvedValueOnce('my-workspace');
149
142
 
150
143
  await main();
151
144
 
@@ -159,7 +152,7 @@ describe('list-workspaces main', () => {
159
152
  it('does not write resume flag when workspace has no session', async () => {
160
153
  mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('my-workspace'));
161
154
  mockGetWorkspaceSessions.mockResolvedValueOnce([]);
162
- mockPrompt.mockResolvedValueOnce({ selected: 'my-workspace' });
155
+ mockSearch.mockResolvedValueOnce('my-workspace');
163
156
 
164
157
  await main();
165
158
 
@@ -173,11 +166,11 @@ describe('list-workspaces main', () => {
173
166
  process.argv = ['node', 'list-workspaces.js', '--archived'];
174
167
  const archived = makeArchivedWorkspaceList('old-ws');
175
168
  mockListWorkspaces.mockResolvedValueOnce(archived);
176
- mockPrompt.mockResolvedValueOnce({ selected: 'old-ws' });
169
+ mockSearch.mockResolvedValueOnce('old-ws');
177
170
 
178
171
  await main();
179
172
 
180
- expect(mockPrompt).toHaveBeenCalledTimes(1);
173
+ expect(mockSearch).toHaveBeenCalledTimes(1);
181
174
  expect(mockWriteFile).toHaveBeenCalledWith(
182
175
  expect.stringContaining('.workspace-last-go'),
183
176
  '/test-workspaces/old-ws',
@@ -188,7 +181,7 @@ describe('list-workspaces main', () => {
188
181
  it('displays workspace name in the list output', async () => {
189
182
  const logSpy = vi.spyOn(console, 'log');
190
183
  mockListWorkspaces.mockResolvedValueOnce(makeWorkspaceList('ws-a'));
191
- mockPrompt.mockResolvedValueOnce({ selected: 'ws-a' });
184
+ mockSearch.mockResolvedValueOnce('ws-a');
192
185
 
193
186
  await main();
194
187
 
@@ -203,7 +196,7 @@ describe('list-workspaces main', () => {
203
196
 
204
197
  await main();
205
198
 
206
- expect(mockPrompt).not.toHaveBeenCalled();
199
+ expect(mockSearch).not.toHaveBeenCalled();
207
200
  expect(writeSpy).toHaveBeenCalledTimes(1);
208
201
  const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
209
202
  expect(payload.count).toBe(2);
@@ -220,7 +213,7 @@ describe('list-workspaces main', () => {
220
213
  await main();
221
214
 
222
215
  expect(logInfo).not.toHaveBeenCalled();
223
- expect(mockPrompt).not.toHaveBeenCalled();
216
+ expect(mockSearch).not.toHaveBeenCalled();
224
217
  const payload = JSON.parse(writeSpy.mock.calls[0][0] as string);
225
218
  expect(payload).toEqual({ archived: false, count: 0, workspaces: [] });
226
219
  writeSpy.mockRestore();
@@ -235,14 +228,14 @@ describe('list-workspaces main', () => {
235
228
  lastActiveAt: new Date(),
236
229
  lastActiveLabel: '5m ago',
237
230
  }]);
238
- mockPrompt.mockResolvedValueOnce({ selected: 'has-session' });
231
+ mockSearch.mockResolvedValueOnce('has-session');
239
232
 
240
233
  await main();
241
234
 
242
235
  // The prompt source function should show has-session first
243
- const promptCall = mockPrompt.mock.calls[0][0][0];
236
+ const promptCall = mockSearch.mock.calls[0][0];
244
237
  const source = promptCall.source;
245
- const items = await source(null, '');
238
+ const items = await source('');
246
239
  expect(items[0].value).toBe('has-session');
247
240
  expect(items[1].value).toBe('no-session');
248
241
  });
@@ -7,12 +7,9 @@ import { getWorkspaceSessions } from '../utils/claude-sessions';
7
7
  import { logInfo, logError } from '../utils/logger';
8
8
  import { outputJson } from '../utils/output';
9
9
  import { colorize } from '../utils/colors';
10
- import inquirer from 'inquirer';
11
- import autocompletePrompt from 'inquirer-autocomplete-prompt';
10
+ import { search } from '../utils/prompt';
12
11
  import * as fuzzy from 'fuzzy';
13
12
 
14
- inquirer.registerPrompt('autocomplete', autocompletePrompt);
15
-
16
13
  const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
17
14
  const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
18
15
 
@@ -136,33 +133,29 @@ async function handleList(opts: { archived?: boolean; json?: boolean }) {
136
133
  console.log('');
137
134
 
138
135
  if (items.length > 0 && (process.stdout.isTTY || process.env.VITEST === 'true')) {
139
- const { selected } = await inquirer.prompt([
140
- {
141
- type: 'autocomplete',
142
- name: 'selected',
143
- message: 'Select workspace to open:',
144
- source: async (_answersSoFar: any, input: string | undefined) => {
145
- const searchInput = input || '';
146
-
147
- const source = items.map(item => ({
148
- name: `${item.name} ${item.lastActiveLabel ? colorize(item.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${item.repoCount} repos`, 'dim')}`,
149
- value: item.name,
150
- }));
151
-
152
- if (!searchInput) return source;
153
-
154
- const results = fuzzy.filter(searchInput, items, {
155
- extract: (item) => item.name,
156
- });
157
-
158
- return results.map(result => ({
159
- name: `${result.original.name} ${result.original.lastActiveLabel ? colorize(result.original.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${result.original.repoCount} repos`, 'dim')}`,
160
- value: result.original.name,
161
- }));
162
- },
163
- pageSize: 15,
164
- } as any,
165
- ]);
136
+ const selected = await search<string>({
137
+ message: 'Select workspace to open:',
138
+ pageSize: 15,
139
+ source: async (term: string | undefined) => {
140
+ const searchInput = term || '';
141
+
142
+ const source = items.map(item => ({
143
+ name: `${item.name} ${item.lastActiveLabel ? colorize(item.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${item.repoCount} repos`, 'dim')}`,
144
+ value: item.name,
145
+ }));
146
+
147
+ if (!searchInput) return source;
148
+
149
+ const results = fuzzy.filter(searchInput, items, {
150
+ extract: (item) => item.name,
151
+ });
152
+
153
+ return results.map(result => ({
154
+ name: `${result.original.name} ${result.original.lastActiveLabel ? colorize(result.original.lastActiveLabel, 'dim') : colorize('no session', 'dim')} ${colorize(`${result.original.repoCount} repos`, 'dim')}`,
155
+ value: result.original.name,
156
+ }));
157
+ },
158
+ });
166
159
 
167
160
  const selectedItem = items.find(i => i.name === selected);
168
161
  if (selectedItem) {
@@ -6,13 +6,10 @@ import { loadMetadata, saveMetadata } from '../utils/workspace-meta';
6
6
  import { generateClaudeContext } from '../utils/claude-integration';
7
7
  import { logInfo, logSuccess, logError, logStep } from '../utils/logger';
8
8
  import { colorize } from '../utils/colors';
9
- import inquirer from 'inquirer';
10
- import autocompletePrompt from 'inquirer-autocomplete-prompt';
9
+ import { confirm, search } from '../utils/prompt';
11
10
  import * as fuzzy from 'fuzzy';
12
11
  import { getGlobalOpts, resolveWorkspace, parseList } from '../utils/command-helpers';
13
12
 
14
- inquirer.registerPrompt('autocomplete', autocompletePrompt);
15
-
16
13
  export function registerRemoveRepoCommand(parent: Command) {
17
14
  parent
18
15
  .command('remove-repo')
@@ -75,10 +72,9 @@ async function handleRemoveRepo(opts: {
75
72
  await fs.rm(workspacePath, { recursive: true, force: true });
76
73
  logSuccess(`Workspace "${workspaceName}" deleted.`);
77
74
  } else {
78
- const { deleteWorkspace } = await inquirer.prompt([{
79
- type: 'confirm', name: 'deleteWorkspace',
75
+ const deleteWorkspace = await confirm({
80
76
  message: `Delete the entire workspace folder "${workspaceName}"?`, default: false,
81
- }]);
77
+ });
82
78
  if (deleteWorkspace) {
83
79
  await fs.rm(workspacePath, { recursive: true, force: true });
84
80
  logSuccess(`Workspace "${workspaceName}" deleted.`);
@@ -115,11 +111,11 @@ async function handleRemoveRepo(opts: {
115
111
  }
116
112
 
117
113
  try {
118
- const { repoDir } = await inquirer.prompt([{
119
- type: 'autocomplete', name: 'repoDir',
114
+ const repoDir = await search<string>({
120
115
  message: `Search and select repository (${colorize(String(selectedDirs.length), 'cyan')} selected):`,
121
- source: async (_answersSoFar: any, input: string | undefined) => {
122
- const searchInput = input || '';
116
+ pageSize: 16,
117
+ source: async (term: string | undefined) => {
118
+ const searchInput = term || '';
123
119
  const doneOption = { name: colorize('done - Finish selection', 'green'), value: 'done' };
124
120
  if (!searchInput || searchInput.toLowerCase().startsWith('done')) {
125
121
  return [doneOption, ...available.map(e => ({ name: e.displayName, value: e.dirName }))];
@@ -127,8 +123,7 @@ async function handleRemoveRepo(opts: {
127
123
  const results = fuzzy.filter(searchInput, available, { extract: (e) => e.displayName });
128
124
  return [doneOption, ...results.map(result => ({ name: result.original.displayName, value: result.original.dirName }))];
129
125
  },
130
- pageSize: 16,
131
- } as any]);
126
+ });
132
127
 
133
128
  if (repoDir === 'done') {
134
129
  if (selectedDirs.length === 0) { console.log(colorize('\nYou must select at least one repository\n', 'yellow')); continue; }
@@ -147,11 +142,10 @@ async function handleRemoveRepo(opts: {
147
142
  console.log('');
148
143
 
149
144
  if (!opts.yes) {
150
- const { confirm } = await inquirer.prompt([{
151
- type: 'confirm', name: 'confirm',
145
+ const confirmed = await confirm({
152
146
  message: `Remove ${selectedDirs.length} instance(s)? This will delete the directories.`, default: false,
153
- }]);
154
- if (!confirm) { logInfo('Removal cancelled'); process.exit(0); }
147
+ });
148
+ if (!confirmed) { logInfo('Removal cancelled'); process.exit(0); }
155
149
  }
156
150
 
157
151
  logStep(4, 4, 'Removing instances...');
@@ -6,7 +6,7 @@ import { logInfo, logSuccess, logError } from '../utils/logger';
6
6
  import { colorize } from '../utils/colors';
7
7
  import { loadMetadata, saveMetadata, listWorkspaces } from '../utils/workspace-meta';
8
8
  import { formatContextFile, appendToContextFile } from '../utils/context-file';
9
- import inquirer from 'inquirer';
9
+ import { select } from '../utils/prompt';
10
10
 
11
11
  const CONTEXT_FILENAME = 'CONTEXT.md';
12
12
 
@@ -66,12 +66,10 @@ async function resolveWorkspacePath(workspaceName?: string): Promise<{ name: str
66
66
  logError('No workspaces found');
67
67
  return null;
68
68
  }
69
- const { selected } = await inquirer.prompt([{
70
- type: 'list',
71
- name: 'selected',
69
+ const selected = await select({
72
70
  message: 'Select workspace:',
73
71
  choices: workspaces.map(w => ({ name: w.name, value: w.name })),
74
- }]);
72
+ });
75
73
  return { name: selected, path: path.join(WORKSPACES_DIR, selected) };
76
74
  }
77
75
 
@@ -7,12 +7,9 @@ import { listWorkspaces } from '../utils/workspace-meta';
7
7
  import { logError, logInfo } from '../utils/logger';
8
8
  import { outputJson, outputJsonError } from '../utils/output';
9
9
  import { colorize } from '../utils/colors';
10
- import inquirer from 'inquirer';
11
- import autocompletePrompt from 'inquirer-autocomplete-prompt';
10
+ import { search } from '../utils/prompt';
12
11
  import * as fuzzy from 'fuzzy';
13
12
 
14
- inquirer.registerPrompt('autocomplete', autocompletePrompt);
15
-
16
13
  const TEMP_FILE = path.join(os.homedir(), '.workspace-last-go');
17
14
  const RESUME_FLAG_FILE = path.join(os.homedir(), '.workspace-resume-session');
18
15
 
@@ -85,11 +82,11 @@ async function handleSessions(opts: { json?: boolean } = {}) {
85
82
  }
86
83
  console.log('');
87
84
 
88
- const { selected } = await inquirer.prompt([{
89
- type: 'autocomplete', name: 'selected',
85
+ const selected = await search<WorkspaceSession>({
90
86
  message: 'Select workspace to resume:',
91
- source: async (_answersSoFar: any, input: string | undefined) => {
92
- const searchInput = input || '';
87
+ pageSize: 15,
88
+ source: async (term: string | undefined) => {
89
+ const searchInput = term || '';
93
90
  const source = items.map(item => ({
94
91
  name: `${item.session.workspaceName} ${colorize(item.session.lastActiveLabel, 'dim')} ${colorize(item.repoLabel, 'dim')}`,
95
92
  value: item.session,
@@ -101,8 +98,7 @@ async function handleSessions(opts: { json?: boolean } = {}) {
101
98
  value: result.original.session,
102
99
  }));
103
100
  },
104
- pageSize: 15,
105
- } as any]);
101
+ });
106
102
 
107
103
  const session = selected as WorkspaceSession;
108
104
  await fs.writeFile(TEMP_FILE, session.workspacePath, 'utf-8');
@@ -7,37 +7,27 @@ import { logInfo, logSuccess, logError, logStep } from '../../utils/logger';
7
7
  import { colorize } from '../../utils/colors';
8
8
  import { saveSuite, getSuite, validateSuiteName } from '../../utils/suite';
9
9
  import { SuiteEntry, WorkspaceSuite, PostCloneHook } from '../../types';
10
- import inquirer from 'inquirer';
10
+ import { confirm, input, select } from '../../utils/prompt';
11
11
 
12
12
  async function promptSuiteDetails(defaultName?: string): Promise<{ name: string; description: string }> {
13
- const { name, description } = await inquirer.prompt([
14
- {
15
- type: 'input',
16
- name: 'name',
17
- message: 'Suite name:',
18
- default: defaultName,
19
- validate: (input: string) => validateSuiteName(input.trim()),
20
- filter: (input: string) => input.trim(),
21
- },
22
- {
23
- type: 'input',
24
- name: 'description',
25
- message: 'Suite description (optional):',
26
- },
27
- ]);
13
+ const name = (await input({
14
+ message: 'Suite name:',
15
+ default: defaultName,
16
+ validate: (input: string) => validateSuiteName(input.trim()),
17
+ })).trim();
18
+
19
+ const description = await input({
20
+ message: 'Suite description (optional):',
21
+ });
28
22
 
29
23
  return { name, description: description || '' };
30
24
  }
31
25
 
32
26
  async function promptPostCloneHooks(): Promise<PostCloneHook[] | undefined> {
33
- const { addHooks } = await inquirer.prompt([
34
- {
35
- type: 'confirm',
36
- name: 'addHooks',
37
- message: 'Add post-clone hooks?',
38
- default: false,
39
- },
40
- ]);
27
+ const addHooks = await confirm({
28
+ message: 'Add post-clone hooks?',
29
+ default: false,
30
+ });
41
31
 
42
32
  if (!addHooks) return undefined;
43
33
 
@@ -45,13 +35,9 @@ async function promptPostCloneHooks(): Promise<PostCloneHook[] | undefined> {
45
35
  console.log('Enter commands one at a time. Type "done" when finished.\n');
46
36
 
47
37
  while (true) {
48
- const { command } = await inquirer.prompt([
49
- {
50
- type: 'input',
51
- name: 'command',
52
- message: `Command ${commands.length + 1} (or "done"):`,
53
- },
54
- ]);
38
+ const command = await input({
39
+ message: `Command ${commands.length + 1} (or "done"):`,
40
+ });
55
41
 
56
42
  if (command.trim().toLowerCase() === 'done') break;
57
43
  if (command.trim()) {
@@ -71,14 +57,10 @@ async function checkOverwrite(name: string): Promise<boolean> {
71
57
  const existing = await getSuite(name);
72
58
  if (!existing) return true;
73
59
 
74
- const { overwrite } = await inquirer.prompt([
75
- {
76
- type: 'confirm',
77
- name: 'overwrite',
78
- message: `Suite "${name}" already exists (${existing.entries.length} repos). Overwrite?`,
79
- default: false,
80
- },
81
- ]);
60
+ const overwrite = await confirm({
61
+ message: `Suite "${name}" already exists (${existing.entries.length} repos). Overwrite?`,
62
+ default: false,
63
+ });
82
64
 
83
65
  return overwrite;
84
66
  }
@@ -211,17 +193,13 @@ export async function main() {
211
193
  console.log('='.repeat(60) + '\n');
212
194
 
213
195
  try {
214
- const { mode } = await inquirer.prompt([
215
- {
216
- type: 'list',
217
- name: 'mode',
218
- message: 'How would you like to create the suite?',
219
- choices: [
220
- { name: 'Pick repositories interactively', value: 'interactive' },
221
- { name: 'Save from an existing workspace', value: 'workspace' },
222
- ],
223
- },
224
- ]);
196
+ const mode = await select({
197
+ message: 'How would you like to create the suite?',
198
+ choices: [
199
+ { name: 'Pick repositories interactively', value: 'interactive' },
200
+ { name: 'Save from an existing workspace', value: 'workspace' },
201
+ ],
202
+ });
225
203
 
226
204
  if (mode === 'interactive') {
227
205
  await createInteractive();
@@ -3,7 +3,7 @@
3
3
  import { listSuites, deleteSuite } from '../../utils/suite';
4
4
  import { logInfo, logSuccess, logError } from '../../utils/logger';
5
5
  import { colorize } from '../../utils/colors';
6
- import inquirer from 'inquirer';
6
+ import { confirm, select } from '../../utils/prompt';
7
7
 
8
8
  export async function main() {
9
9
  console.log('\n' + '='.repeat(60));
@@ -18,28 +18,19 @@ export async function main() {
18
18
  process.exit(0);
19
19
  }
20
20
 
21
- const { suiteName } = await inquirer.prompt([
22
- {
23
- type: 'list',
24
- name: 'suiteName',
25
- message: 'Select a suite to delete:',
26
- choices: suites.map(s => ({
27
- name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
28
- value: s.name,
29
- short: s.name,
30
- })),
31
- pageSize: 15,
32
- },
33
- ]);
34
-
35
- const { confirmed } = await inquirer.prompt([
36
- {
37
- type: 'confirm',
38
- name: 'confirmed',
39
- message: `Are you sure you want to delete suite "${suiteName}"?`,
40
- default: false,
41
- },
42
- ]);
21
+ const suiteName = await select({
22
+ message: 'Select a suite to delete:',
23
+ choices: suites.map(s => ({
24
+ name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
25
+ value: s.name,
26
+ })),
27
+ pageSize: 15,
28
+ });
29
+
30
+ const confirmed = await confirm({
31
+ message: `Are you sure you want to delete suite "${suiteName}"?`,
32
+ default: false,
33
+ });
43
34
 
44
35
  if (!confirmed) {
45
36
  logInfo('Deletion cancelled');
@@ -5,7 +5,7 @@ import * as path from 'path';
5
5
  import { listSuites, exportSuite, exportAllSuites } from '../../utils/suite';
6
6
  import { logInfo, logSuccess, logError } from '../../utils/logger';
7
7
  import { colorize } from '../../utils/colors';
8
- import inquirer from 'inquirer';
8
+ import { input, select } from '../../utils/prompt';
9
9
 
10
10
  export async function main(opts?: { file?: string }) {
11
11
  console.log('\n' + '='.repeat(60));
@@ -20,35 +20,26 @@ export async function main(opts?: { file?: string }) {
20
20
  process.exit(0);
21
21
  }
22
22
 
23
- const { mode } = await inquirer.prompt([
24
- {
25
- type: 'list',
26
- name: 'mode',
27
- message: 'What would you like to export?',
28
- choices: [
29
- { name: 'A single suite', value: 'single' },
30
- { name: 'All suites', value: 'all' },
31
- ],
32
- },
33
- ]);
23
+ const mode = await select({
24
+ message: 'What would you like to export?',
25
+ choices: [
26
+ { name: 'A single suite', value: 'single' },
27
+ { name: 'All suites', value: 'all' },
28
+ ],
29
+ });
34
30
 
35
31
  let data;
36
32
  let defaultFilename: string;
37
33
 
38
34
  if (mode === 'single') {
39
- const { suiteName } = await inquirer.prompt([
40
- {
41
- type: 'list',
42
- name: 'suiteName',
43
- message: 'Select a suite to export:',
44
- choices: suites.map(s => ({
45
- name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
46
- value: s.name,
47
- short: s.name,
48
- })),
49
- pageSize: 15,
50
- },
51
- ]);
35
+ const suiteName = await select({
36
+ message: 'Select a suite to export:',
37
+ choices: suites.map(s => ({
38
+ name: `${s.name} (${s.entries.length} repos)${s.description ? ` - ${s.description}` : ''}`,
39
+ value: s.name,
40
+ })),
41
+ pageSize: 15,
42
+ });
52
43
 
53
44
  data = await exportSuite(suiteName);
54
45
  if (!data) {
@@ -68,14 +59,10 @@ export async function main(opts?: { file?: string }) {
68
59
  if (outputArg && !outputArg.startsWith('-')) {
69
60
  outputPath = path.resolve(outputArg);
70
61
  } else {
71
- const { filePath } = await inquirer.prompt([
72
- {
73
- type: 'input',
74
- name: 'filePath',
75
- message: 'Output file path:',
76
- default: `./${defaultFilename}`,
77
- },
78
- ]);
62
+ const filePath = await input({
63
+ message: 'Output file path:',
64
+ default: `./${defaultFilename}`,
65
+ });
79
66
  outputPath = path.resolve(filePath);
80
67
  }
81
68
 
@@ -6,7 +6,7 @@ import { importSuites } from '../../utils/suite';
6
6
  import { logInfo, logSuccess, logError, logWarning } from '../../utils/logger';
7
7
  import { colorize } from '../../utils/colors';
8
8
  import { SuitesStore } from '../../types';
9
- import inquirer from 'inquirer';
9
+ import { confirm, input } from '../../utils/prompt';
10
10
 
11
11
  export async function main(opts?: { file?: string }) {
12
12
  console.log('\n' + '='.repeat(60));
@@ -20,14 +20,10 @@ export async function main(opts?: { file?: string }) {
20
20
  if (fileArg && !fileArg.startsWith('-')) {
21
21
  filePath = path.resolve(fileArg);
22
22
  } else {
23
- const { inputPath } = await inquirer.prompt([
24
- {
25
- type: 'input',
26
- name: 'inputPath',
27
- message: 'Path to suite JSON file:',
28
- validate: (input: string) => input.trim().length > 0 || 'File path is required',
29
- },
30
- ]);
23
+ const inputPath = await input({
24
+ message: 'Path to suite JSON file:',
25
+ validate: (input: string) => input.trim().length > 0 || 'File path is required',
26
+ });
31
27
  filePath = path.resolve(inputPath);
32
28
  }
33
29
 
@@ -61,14 +57,10 @@ export async function main(opts?: { file?: string }) {
61
57
  }
62
58
  console.log('');
63
59
 
64
- const { overwrite } = await inquirer.prompt([
65
- {
66
- type: 'confirm',
67
- name: 'overwrite',
68
- message: 'Overwrite existing suites with the same name?',
69
- default: false,
70
- },
71
- ]);
60
+ const overwrite = await confirm({
61
+ message: 'Overwrite existing suites with the same name?',
62
+ default: false,
63
+ });
72
64
 
73
65
  const result = await importSuites(data, overwrite);
74
66