Skip to content

Commit 910d4e0

Browse files
committed
feat: enhance npm package search with error handling and debounce
- Added debounce functionality to the npm package search input to reduce rate limiting issues. - Implemented error handling for npm registry search, providing user feedback in case of failures. - Updated environment variable references to use KIT prefix for consistency across the application. - Enhanced tests to cover new error handling scenarios and environment variable changes.
1 parent f1a8cae commit 910d4e0

File tree

7 files changed

+95
-177
lines changed

7 files changed

+95
-177
lines changed

src/cli/install.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ if (!global.confirmedPackages) {
4848
let packages = await arg(
4949
{
5050
enter: "Install",
51+
debounceInput: 1000,
52+
hint: 'Note: npm search is debounced 1 second to avoid rate limiting',
5153
placeholder:
5254
"Which npm package would you like to install?",
5355
},
@@ -70,20 +72,32 @@ if (!global.confirmedPackages) {
7072
}
7173
}[]
7274
}
73-
let response = await get<pkgs>(
74-
`http://registry.npmjs.com/-/v1/search?text=${input}&size=20`
75-
)
76-
let packages = response.data.objects
77-
return packages.map(o => {
78-
return {
79-
name: o.package.name,
80-
value: o.package.name,
81-
description: `${o.package.description
82-
} - ${formatDistanceToNow(
83-
parseISO(o.package.date)
84-
)} ago`,
85-
}
86-
})
75+
try {
76+
let response = await get<pkgs>(
77+
`http://registry.npmjs.com/-/v1/search?text=${input}&size=20`
78+
)
79+
let packages = response.data.objects
80+
return packages.map(o => {
81+
return {
82+
name: o.package.name,
83+
value: o.package.name,
84+
description: `${o.package.description
85+
} - ${formatDistanceToNow(
86+
parseISO(o.package.date)
87+
)} ago`,
88+
}
89+
})
90+
} catch (error) {
91+
console.error('Error searching npm registry:', error.message || error)
92+
return [
93+
{
94+
info: true,
95+
miss: true,
96+
name: `Error: ${error.message || 'Failed to search npm packages'}`,
97+
description: 'Please try again or install manually',
98+
}
99+
]
100+
}
87101
}
88102
)
89103

src/lib/ai-env-integration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ ava('resolveModel should create Anthropic model with prefix', async t => {
4141
ava('resolveModel should use default provider when no prefix', async t => {
4242
// Set API key for default provider (openai)
4343
process.env.OPENAI_API_KEY = 'test-openai-key'
44-
process.env.AI_DEFAULT_PROVIDER = 'openai'
44+
process.env.KIT_AI_DEFAULT_PROVIDER = 'openai'
4545

4646
try {
4747
const model = await resolveModel('some-model')
@@ -52,7 +52,7 @@ ava('resolveModel should use default provider when no prefix', async t => {
5252
t.is(model.modelId, 'some-model')
5353
} finally {
5454
delete process.env.OPENAI_API_KEY
55-
delete process.env.AI_DEFAULT_PROVIDER
55+
delete process.env.KIT_AI_DEFAULT_PROVIDER
5656
}
5757
})
5858

src/lib/ai.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,8 +1442,8 @@ test.serial('backward compatibility: both global.generate and ai.object should b
14421442
// Tests for new AI provider resolution functionality
14431443
test.serial('resolveModel should use OpenAI as default when no env vars are set', async t => {
14441444
// Clear any existing env vars
1445-
delete process.env.AI_DEFAULT_PROVIDER;
1446-
delete process.env.AI_DEFAULT_MODEL;
1445+
delete process.env.KIT_AI_DEFAULT_PROVIDER;
1446+
delete process.env.KIT_AI_DEFAULT_MODEL;
14471447

14481448
const mockResult = createMockGenerateTextResult({ text: "OpenAI default response" });
14491449
mockGenerateText.resolves(mockResult);
@@ -1460,8 +1460,8 @@ test.serial('resolveModel should use OpenAI as default when no env vars are set'
14601460
});
14611461

14621462
test.serial('resolveModel should use custom default provider from environment', async t => {
1463-
process.env.AI_DEFAULT_PROVIDER = 'anthropic';
1464-
process.env.AI_DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
1463+
process.env.KIT_AI_DEFAULT_PROVIDER = 'anthropic';
1464+
process.env.KIT_AI_DEFAULT_MODEL = 'claude-3-5-sonnet-latest';
14651465

14661466
const mockResult = createMockGenerateTextResult({ text: "Anthropic response" });
14671467
mockGenerateText.resolves(mockResult);
@@ -1477,8 +1477,8 @@ test.serial('resolveModel should use custom default provider from environment',
14771477
t.true(mockGenerateText.calledOnce);
14781478

14791479
// Clean up
1480-
delete process.env.AI_DEFAULT_PROVIDER;
1481-
delete process.env.AI_DEFAULT_MODEL;
1480+
delete process.env.KIT_AI_DEFAULT_PROVIDER;
1481+
delete process.env.KIT_AI_DEFAULT_MODEL;
14821482
});
14831483

14841484
test.serial('resolveModel should support provider prefix syntax', async t => {

src/lib/ai.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ const PROVIDERS: Record<AIProvider, ModelFactory> = {
6262
};
6363

6464
// Cache environment variables at module load
65-
const ENV_PROVIDER = (process.env.AI_DEFAULT_PROVIDER ?? 'openai') as AIProvider;
66-
const ENV_MODEL = process.env.AI_DEFAULT_MODEL ?? 'gpt-4o';
65+
const ENV_PROVIDER = (process.env.KIT_AI_DEFAULT_PROVIDER ?? 'openai') as AIProvider;
66+
const ENV_MODEL = process.env.KIT_AI_DEFAULT_MODEL ?? 'gpt-4o';
6767

6868
// Cache for prompted API keys during session
6969
const apiKeyCache = new Map<string, boolean>();
@@ -282,7 +282,7 @@ interface AiGlobal {
282282

283283
// This is the actual function that creates the AI-powered input handler
284284
const aiPoweredInputHandlerFactory = (systemPrompt: string, options: Omit<AiOptions, 'autoExecuteTools' | 'tools' | 'maxSteps'> = {}) => {
285-
const { model, temperature = Number(process.env.AI_DEFAULT_TEMPERATURE) || 0.7, maxTokens = Number(process.env.AI_DEFAULT_MAX_TOKENS) || 1000 } = options;
285+
const { model, temperature = Number(process.env.KIT_AI_DEFAULT_TEMPERATURE) || 0.7, maxTokens = Number(process.env.KIT_AI_DEFAULT_MAX_TOKENS) || 1000 } = options;
286286

287287
return async (input: string): Promise<string> => {
288288
try {
@@ -372,8 +372,8 @@ const createAssistantInstance = (systemPrompt: string, options: AiOptions = {}):
372372
};
373373

374374
const {
375-
temperature = 0.7,
376-
maxTokens = 1000,
375+
temperature = Number(process.env.KIT_AI_DEFAULT_TEMPERATURE) || 0.7,
376+
maxTokens = Number(process.env.KIT_AI_DEFAULT_MAX_TOKENS) || 1000,
377377
tools: providedTools,
378378
maxSteps = 3,
379379
autoExecuteTools: initialAutoExecuteTools = true, // Default to true

src/main/common.ts

Lines changed: 18 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ export let actionFlags: ActionFlag[] = [
158158
hint: `Select destination for ${path.basename(selectedFile)}`,
159159
startPath: home(),
160160
onlyDirs: true,
161+
enter: 'Copy',
161162
shortcuts: [escapeShortcut]
162163
})
163164
await copyFile(selectedFile, path.resolve(destination, path.basename(selectedFile)))
@@ -171,12 +172,13 @@ export let actionFlags: ActionFlag[] = [
171172
const base = path.basename(selectedFile)
172173
let destination = await path({
173174
hint: `Select path (including new filename) for ${base}`,
174-
startPath: home(),
175-
onlyDirs: true,
175+
startPath: path.dirname(selectedFile),
176+
onlyDirs: false,
177+
enter: 'Duplicate',
176178
shortcuts: [escapeShortcut],
177179
missingChoices: [
178180
{
179-
name: `Duplicate "${base}" to "{input}"`,
181+
name: `Duplicate "{base}" to "{input}"`,
180182
miss: true,
181183
enter: 'Duplicate',
182184
onSubmit: (input) => {
@@ -208,7 +210,8 @@ export let actionFlags: ActionFlag[] = [
208210
let destFolder = await path({
209211
startPath: path.dirname(selectedFile),
210212
hint: `Select destination for ${path.basename(selectedFile)}`,
211-
onlyDirs: true
213+
onlyDirs: true,
214+
enter: 'Move'
212215
})
213216
mv(selectedFile, destFolder)
214217
}
@@ -218,132 +221,21 @@ export let actionFlags: ActionFlag[] = [
218221
value: "move-and-rename",
219222
shortcut: `${cmd}+shift+m`,
220223
action: async (selectedFile) => {
221-
const baseName = path.basename(selectedFile)
222-
const dirName = path.dirname(selectedFile)
223-
224-
// Track whether we're in "create mode"
225-
let createMode = false
226-
let lastValidDir = dirName
227-
228-
let newPath = await path({
224+
const baseName = path.basename(selectedFile);
225+
const dirName = path.dirname(selectedFile);
226+
227+
await path({
229228
startPath: dirName,
230229
hint: `Enter new path/name for "${baseName}"`,
231230
enter: 'Move & Rename',
232-
233-
// Override the default onInput behavior
234-
onInput: async (input, state) => {
235-
if (!input) return
236-
237-
// Handle home directory expansion
238-
if (input.startsWith('~')) {
239-
input = home() + input.slice(1)
240-
await setInput(input)
241-
return
242-
}
243-
244-
// Extract directory and filename parts
245-
const inputDir = input.endsWith(path.sep) ? input : path.dirname(input)
246-
const inputName = input.endsWith(path.sep) ? '' : path.basename(input)
247-
248-
// Check if the directory exists
249-
const dirExists = await isDir(inputDir)
250-
251-
if (dirExists && inputDir !== lastValidDir) {
252-
// Directory exists and changed - load its contents
253-
lastValidDir = inputDir
254-
createMode = false
255-
256-
// Load directory contents
257-
const choices = await createPathChoices(inputDir, { onlyDirs: false })
258-
259-
// Add the "create new file" option if user typed a filename
260-
if (inputName) {
261-
choices.unshift({
262-
name: `Create: "${inputName}"`,
263-
value: input,
264-
description: 'New file will be created',
265-
enter: 'Create & Move',
266-
miss: true,
267-
img: pathToFileURL(kitPath('icons', 'file.svg')).href
268-
})
269-
}
270-
271-
await setChoices(choices)
272-
setPanel('') // Clear any previous messages
273-
274-
} else if (!dirExists && !createMode) {
275-
// Directory doesn't exist - enter create mode
276-
createMode = true
277-
278-
// Show helpful message instead of error
279-
setPanel(md(`### New path will be created
280-
281-
**Directory:** ${inputDir}
282-
**File:** ${inputName || 'Enter filename...'}
283-
284-
Press **Enter** to create and move the file.`))
285-
286-
// Set choices to just the create option
287-
await setChoices([{
288-
name: `Create path: "${input}"`,
289-
value: input,
290-
description: 'This path will be created',
291-
enter: 'Create & Move',
292-
miss: true,
293-
img: pathToFileURL(kitPath('icons', 'folder.svg')).href
294-
}])
295-
296-
} else if (createMode && inputName) {
297-
// Update the create option with current input
298-
await setChoices([{
299-
name: `Create path: "${input}"`,
300-
value: input,
301-
description: 'This path will be created',
302-
enter: 'Create & Move',
303-
miss: true,
304-
img: pathToFileURL(kitPath('icons', 'file.svg')).href
305-
}])
306-
307-
// Update the panel with current path info
308-
setPanel(md(`### New path will be created
309-
310-
**Directory:** ${inputDir}
311-
**File:** ${inputName}
312-
313-
Press **Enter** to create and move the file.`))
314-
}
315-
316-
// Update enter button text based on mode
317-
setEnter(createMode ? 'Create & Move' : 'Select')
318-
},
319-
320-
// Don't use the default missingChoices
321-
missingChoices: [],
322-
323-
// Custom shortcuts
324-
shortcuts: [
325-
escapeShortcut,
326-
{
327-
name: 'Parent Dir',
328-
key: `${cmd}+up`,
329-
onPress: async (input) => {
330-
const parentDir = path.dirname(path.dirname(input))
331-
await setInput(parentDir + path.sep)
332-
}
333-
}
334-
]
335-
})
336-
337-
if (newPath) {
338-
// Ensure parent directory exists before moving
339-
const newDir = path.dirname(newPath)
340-
if (!(await isDir(newDir))) {
341-
await ensureDir(newDir)
231+
onSubmit: async (selectedPath) => {
232+
// Ensure parent directory exists before moving
233+
await editor(selectedPath);
234+
await ensureDir(path.dirname(selectedPath));
235+
// Perform the move
236+
await mv(selectedFile, selectedPath);
342237
}
343-
344-
// Perform the move
345-
await mv(selectedFile, newPath)
346-
}
238+
});
347239
}
348240
},
349241
{

0 commit comments

Comments
 (0)