hone-ai 0.2.0 → 0.9.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/src/errors.ts CHANGED
@@ -3,9 +3,12 @@
3
3
  */
4
4
 
5
5
  export class HoneError extends Error {
6
- constructor(message: string, public readonly exitCode: number = 1) {
7
- super(message);
8
- this.name = 'HoneError';
6
+ constructor(
7
+ message: string,
8
+ public readonly exitCode: number = 1
9
+ ) {
10
+ super(message)
11
+ this.name = 'HoneError'
9
12
  }
10
13
  }
11
14
 
@@ -13,11 +16,11 @@ export class HoneError extends Error {
13
16
  * Format error message in hone style with ✗ symbol
14
17
  */
15
18
  export function formatError(message: string, details?: string): string {
16
- let output = `✗ ${message}`;
19
+ let output = `✗ ${message}`
17
20
  if (details) {
18
- output += `\n\n${details}`;
21
+ output += `\n\n${details}`
19
22
  }
20
- return output;
23
+ return output
21
24
  }
22
25
 
23
26
  /**
@@ -25,27 +28,27 @@ export function formatError(message: string, details?: string): string {
25
28
  * In test mode (NODE_ENV=test or BUN_ENV=test), throws instead of exiting
26
29
  */
27
30
  export function exitWithError(message: string, details?: string): never {
28
- const fullMessage = formatError(message, details);
29
-
31
+ const fullMessage = formatError(message, details)
32
+
30
33
  // In test mode, throw instead of exit to allow testing
31
34
  if (process.env.NODE_ENV === 'test' || process.env.BUN_ENV === 'test') {
32
- throw new HoneError(fullMessage);
35
+ throw new HoneError(fullMessage)
33
36
  }
34
-
35
- console.error(fullMessage);
36
- process.exit(1);
37
+
38
+ console.error(fullMessage)
39
+ process.exit(1)
37
40
  }
38
41
 
39
42
  /**
40
43
  * Check if error is a network-related error
41
44
  */
42
45
  export function isNetworkError(error: unknown): boolean {
43
- if (!error || typeof error !== 'object') return false;
44
-
45
- const err = error as any;
46
- const message = err.message?.toLowerCase() || '';
47
- const code = err.code?.toLowerCase() || '';
48
-
46
+ if (!error || typeof error !== 'object') return false
47
+
48
+ const err = error as any
49
+ const message = err.message?.toLowerCase() || ''
50
+ const code = err.code?.toLowerCase() || ''
51
+
49
52
  // Common network error codes and messages
50
53
  const networkIndicators = [
51
54
  'econnrefused',
@@ -56,36 +59,36 @@ export function isNetworkError(error: unknown): boolean {
56
59
  'network',
57
60
  'timeout',
58
61
  'fetch failed',
59
- 'socket hang up'
60
- ];
61
-
62
- return networkIndicators.some(indicator =>
63
- message.includes(indicator) || code.includes(indicator)
64
- );
62
+ 'socket hang up',
63
+ ]
64
+
65
+ return networkIndicators.some(
66
+ indicator => message.includes(indicator) || code.includes(indicator)
67
+ )
65
68
  }
66
69
 
67
70
  /**
68
71
  * Check if error indicates rate limiting
69
72
  */
70
73
  export function isRateLimitError(errorText: string): boolean {
71
- const lowerError = errorText.toLowerCase();
74
+ const lowerError = errorText.toLowerCase()
72
75
  const rateLimitIndicators = [
73
76
  'rate limit',
74
77
  'rate_limit',
75
78
  'too many requests',
76
79
  '429',
77
80
  'quota exceeded',
78
- 'rate exceeded'
79
- ];
80
-
81
- return rateLimitIndicators.some(indicator => lowerError.includes(indicator));
81
+ 'rate exceeded',
82
+ ]
83
+
84
+ return rateLimitIndicators.some(indicator => lowerError.includes(indicator))
82
85
  }
83
86
 
84
87
  /**
85
88
  * Check if error indicates model unavailability
86
89
  */
87
90
  export function isModelUnavailableError(errorText: string): boolean {
88
- const lowerError = errorText.toLowerCase();
91
+ const lowerError = errorText.toLowerCase()
89
92
  const modelErrorIndicators = [
90
93
  'model not found',
91
94
  'model unavailable',
@@ -93,48 +96,48 @@ export function isModelUnavailableError(errorText: string): boolean {
93
96
  'invalid model',
94
97
  'unknown model',
95
98
  '404',
96
- 'not found'
97
- ];
98
-
99
- return modelErrorIndicators.some(indicator => lowerError.includes(indicator));
99
+ 'not found',
100
+ ]
101
+
102
+ return modelErrorIndicators.some(indicator => lowerError.includes(indicator))
100
103
  }
101
104
 
102
105
  /**
103
106
  * Parse structured error information from agent stderr
104
107
  */
105
108
  export interface AgentErrorInfo {
106
- type: 'network' | 'rate_limit' | 'model_unavailable' | 'spawn_failed' | 'timeout' | 'unknown';
107
- retryable: boolean;
108
- retryAfter?: number;
109
+ type: 'network' | 'rate_limit' | 'model_unavailable' | 'spawn_failed' | 'timeout' | 'unknown'
110
+ retryable: boolean
111
+ retryAfter?: number
109
112
  }
110
113
 
111
114
  export function parseAgentError(stderr: string, exitCode?: number): AgentErrorInfo {
112
115
  if (isNetworkError({ message: stderr })) {
113
- return { type: 'network', retryable: true };
116
+ return { type: 'network', retryable: true }
114
117
  }
115
-
118
+
116
119
  if (isRateLimitError(stderr)) {
117
120
  // Try to extract retry-after time from stderr
118
- const retryMatch = stderr.match(/retry[- ]after[:\s]+(\d+)/i);
119
- const retryAfter = retryMatch && retryMatch[1] ? parseInt(retryMatch[1], 10) : undefined;
120
- return { type: 'rate_limit', retryable: false, retryAfter };
121
+ const retryMatch = stderr.match(/retry[- ]after[:\s]+(\d+)/i)
122
+ const retryAfter = retryMatch && retryMatch[1] ? parseInt(retryMatch[1], 10) : undefined
123
+ return { type: 'rate_limit', retryable: false, retryAfter }
121
124
  }
122
-
125
+
123
126
  if (isModelUnavailableError(stderr)) {
124
- return { type: 'model_unavailable', retryable: false };
127
+ return { type: 'model_unavailable', retryable: false }
125
128
  }
126
-
129
+
127
130
  // Check for timeout (exit code 124 or timeout in stderr)
128
131
  if (exitCode === 124 || stderr.toLowerCase().includes('timed out')) {
129
- return { type: 'timeout', retryable: false };
132
+ return { type: 'timeout', retryable: false }
130
133
  }
131
-
134
+
132
135
  // Check for spawn-related failures (typically exit code undefined or ENOENT)
133
136
  if (exitCode === undefined || stderr.toLowerCase().includes('enoent')) {
134
- return { type: 'spawn_failed', retryable: false };
137
+ return { type: 'spawn_failed', retryable: false }
135
138
  }
136
-
137
- return { type: 'unknown', retryable: false };
139
+
140
+ return { type: 'unknown', retryable: false }
138
141
  }
139
142
 
140
143
  /**
@@ -143,44 +146,46 @@ export function parseAgentError(stderr: string, exitCode?: number): AgentErrorIn
143
146
  export async function retryWithBackoff<T>(
144
147
  fn: () => Promise<T>,
145
148
  options: {
146
- maxRetries?: number;
147
- initialDelay?: number;
148
- maxDelay?: number;
149
- shouldRetry?: (error: unknown) => boolean;
149
+ maxRetries?: number
150
+ initialDelay?: number
151
+ maxDelay?: number
152
+ shouldRetry?: (error: unknown) => boolean
150
153
  } = {}
151
154
  ): Promise<T> {
152
155
  const {
153
156
  maxRetries = 3,
154
157
  initialDelay = 1000,
155
158
  maxDelay = 10000,
156
- shouldRetry = isNetworkError
157
- } = options;
159
+ shouldRetry = isNetworkError,
160
+ } = options
161
+
162
+ let lastError: unknown
158
163
 
159
- let lastError: unknown;
160
-
161
164
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
162
165
  try {
163
- return await fn();
166
+ return await fn()
164
167
  } catch (error) {
165
- lastError = error;
166
-
168
+ lastError = error
169
+
167
170
  // Don't retry if not a network error or if we're out of retries
168
171
  if (!shouldRetry(error) || attempt === maxRetries) {
169
- throw error;
172
+ throw error
170
173
  }
171
-
174
+
172
175
  // Calculate delay with exponential backoff
173
- const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
174
-
175
- console.error(`Network error, retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`);
176
-
176
+ const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay)
177
+
178
+ console.error(
179
+ `Network error, retrying in ${delay}ms... (attempt ${attempt + 1}/${maxRetries})`
180
+ )
181
+
177
182
  // Wait before retrying
178
- await new Promise(resolve => setTimeout(resolve, delay));
183
+ await new Promise(resolve => setTimeout(resolve, delay))
179
184
  }
180
185
  }
181
-
186
+
182
187
  // Should never reach here, but TypeScript needs it
183
- throw lastError;
188
+ throw lastError
184
189
  }
185
190
 
186
191
  /**
@@ -192,64 +197,65 @@ export const ErrorMessages = {
192
197
  details: `Please create a .env file in your project root with:
193
198
  ANTHROPIC_API_KEY=your-api-key-here
194
199
 
195
- Get your API key at: https://console.anthropic.com/`
200
+ Get your API key at: https://console.anthropic.com/`,
196
201
  },
197
-
202
+
198
203
  FILE_NOT_FOUND: (path: string) => ({
199
204
  message: `Error: File not found`,
200
205
  details: `Could not find file: ${path}
201
206
 
202
- Please check the path and try again.`
207
+ Please check the path and try again.`,
203
208
  }),
204
-
209
+
205
210
  AGENT_NOT_FOUND: (agent: string) => ({
206
211
  message: `Error: ${agent} command not found`,
207
- details: agent === 'claude'
208
- ? `Please install Claude Code CLI:
212
+ details:
213
+ agent === 'claude'
214
+ ? `Please install Claude Code CLI:
209
215
  npm install -g @anthropic-ai/claude-code
210
216
 
211
217
  Or visit: https://docs.anthropic.com/en/docs/claude-code`
212
- : `Please install OpenCode CLI:
218
+ : `Please install OpenCode CLI:
213
219
  npm install -g @opencode/cli
214
220
 
215
- Or visit: https://opencode.ai/docs/installation`
221
+ Or visit: https://opencode.ai/docs/installation`,
216
222
  }),
217
-
223
+
218
224
  GIT_NOT_INITIALIZED: {
219
225
  message: 'Error: Git repository not initialized',
220
226
  details: `Please initialize git first:
221
- git init`
227
+ git init`,
222
228
  },
223
-
229
+
224
230
  INVALID_TASK_FILE: (path: string, reason: string) => ({
225
231
  message: 'Error: Invalid task file format',
226
232
  details: `File: ${path}
227
233
  Reason: ${reason}
228
234
 
229
- Please ensure the task file follows the correct YAML schema.`
235
+ Please ensure the task file follows the correct YAML schema.`,
230
236
  }),
231
-
237
+
232
238
  NETWORK_ERROR_FINAL: (error: unknown) => {
233
- const message = error instanceof Error ? error.message : String(error);
239
+ const message = error instanceof Error ? error.message : String(error)
234
240
  return {
235
241
  message: 'Error: Network request failed after retries',
236
242
  details: `Failed to connect to Anthropic API after multiple attempts.
237
243
 
238
244
  Error: ${message}
239
245
 
240
- Please check your internet connection and try again.`
241
- };
246
+ Please check your internet connection and try again.`,
247
+ }
242
248
  },
243
-
249
+
244
250
  AGENT_SPAWN_FAILED: (agent: string, error: string) => ({
245
251
  message: `Error: Failed to start ${agent}`,
246
252
  details: `Could not spawn ${agent} agent process.
247
253
 
248
254
  Error: ${error}
249
255
 
250
- Please ensure ${agent} is properly installed and in your PATH.`
256
+ Please ensure ${agent} is properly installed and in your PATH.`,
251
257
  }),
252
-
258
+
253
259
  MODEL_UNAVAILABLE: (model: string, agent: string) => ({
254
260
  message: `Error: Model not available`,
255
261
  details: `The model "${model}" is not available for agent "${agent}".
@@ -260,14 +266,14 @@ Please check:
260
266
  • Your ${agent} CLI is up to date
261
267
 
262
268
  Supported tiers: sonnet, opus
263
- Example: claude-sonnet-4-20250514`
269
+ Example: claude-sonnet-4-20250514`,
264
270
  }),
265
-
271
+
266
272
  RATE_LIMIT_ERROR: (agent: string, retryAfter?: number) => {
267
- const retryMsg = retryAfter
273
+ const retryMsg = retryAfter
268
274
  ? `Please retry after ${retryAfter} seconds.`
269
- : 'Please wait a few minutes before retrying.';
270
-
275
+ : 'Please wait a few minutes before retrying.'
276
+
271
277
  return {
272
278
  message: 'Error: Rate limit exceeded',
273
279
  details: `The ${agent} agent has exceeded its rate limit.
@@ -277,10 +283,10 @@ ${retryMsg}
277
283
  Consider:
278
284
  • Spacing out your requests
279
285
  • Using a different model if available
280
- • Checking your API usage dashboard`
281
- };
286
+ • Checking your API usage dashboard`,
287
+ }
282
288
  },
283
-
289
+
284
290
  AGENT_TIMEOUT: (agent: string, timeout: number) => ({
285
291
  message: `Error: ${agent} agent timed out`,
286
292
  details: `The ${agent} agent did not respond within ${Math.round(timeout / 1000)} seconds.
@@ -293,7 +299,7 @@ This may indicate:
293
299
  Try:
294
300
  • Simplifying your request
295
301
  • Checking your internet connection
296
- • Retrying in a few minutes`
302
+ • Retrying in a few minutes`,
297
303
  }),
298
304
 
299
305
  AGENT_ERROR: (agent: string, exitCode: number, stderr: string) => ({
@@ -308,6 +314,6 @@ This may indicate:
308
314
  • Model configuration issue
309
315
  • Agent internal error
310
316
 
311
- Review the error output above for specific details.`
312
- })
313
- };
317
+ Review the error output above for specific details.`,
318
+ }),
319
+ }