@kb-labs/shared-command-kit 1.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.
- package/LICENSE +22 -0
- package/README.md +1030 -0
- package/dist/analytics/index.d.ts +111 -0
- package/dist/analytics/index.js +56 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/errors/index.d.ts +227 -0
- package/dist/errors/index.js +276 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/flags/index.d.ts +196 -0
- package/dist/flags/index.js +288 -0
- package/dist/flags/index.js.map +1 -0
- package/dist/helpers/index.d.ts +650 -0
- package/dist/helpers/index.js +274 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/index.d.ts +1696 -0
- package/dist/index.js +1543 -0
- package/dist/index.js.map +1 -0
- package/dist/studio/index.js +178 -0
- package/dist/studio/index.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { IAnalytics } from '@kb-labs/core-platform';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analytics Wrapper
|
|
5
|
+
*
|
|
6
|
+
* Optional helper for wrapping operations with analytics events.
|
|
7
|
+
* You can always use ctx.platform.analytics.track() directly - this is just convenience.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { withAnalytics } from '@kb-labs/shared-command-kit';
|
|
12
|
+
*
|
|
13
|
+
* const result = await withAnalytics(ctx, 'mind.query', {
|
|
14
|
+
* started: { queryId, text, mode },
|
|
15
|
+
* completed: (result) => ({ tokensIn: result.tokensIn, tokensOut: result.tokensOut }),
|
|
16
|
+
* failed: (error) => ({ errorMessage: error.message }),
|
|
17
|
+
* }, async () => {
|
|
18
|
+
* return await executeQuery(...);
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Context with optional analytics
|
|
25
|
+
*/
|
|
26
|
+
interface AnalyticsContext {
|
|
27
|
+
platform?: {
|
|
28
|
+
analytics?: IAnalytics;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Analytics event configuration
|
|
33
|
+
*/
|
|
34
|
+
interface AnalyticsEvents<TResult> {
|
|
35
|
+
/** Event properties when operation starts */
|
|
36
|
+
started?: Record<string, unknown>;
|
|
37
|
+
/** Event properties when operation completes (can be function of result) */
|
|
38
|
+
completed?: Record<string, unknown> | ((result: TResult) => Record<string, unknown>);
|
|
39
|
+
/** Event properties when operation fails (can be function of error) */
|
|
40
|
+
failed?: Record<string, unknown> | ((error: Error) => Record<string, unknown>);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wrap an async operation with analytics tracking
|
|
44
|
+
*
|
|
45
|
+
* Automatically tracks:
|
|
46
|
+
* - `{eventName}.started` when operation begins
|
|
47
|
+
* - `{eventName}.completed` when operation succeeds
|
|
48
|
+
* - `{eventName}.failed` when operation fails
|
|
49
|
+
*
|
|
50
|
+
* @param ctx - Context with platform.analytics
|
|
51
|
+
* @param eventName - Base event name (e.g., 'mind.query', 'workflow.run')
|
|
52
|
+
* @param events - Event properties for started/completed/failed events
|
|
53
|
+
* @param operation - Async operation to execute
|
|
54
|
+
* @returns Result of the operation
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const result = await withAnalytics(ctx, 'mind.query', {
|
|
59
|
+
* started: { queryId: '123', text: 'search query' },
|
|
60
|
+
* completed: (result) => ({
|
|
61
|
+
* tokensIn: result.tokensIn,
|
|
62
|
+
* tokensOut: result.tokensOut,
|
|
63
|
+
* resultsCount: result.results.length,
|
|
64
|
+
* }),
|
|
65
|
+
* failed: (error) => ({
|
|
66
|
+
* errorCode: error.code,
|
|
67
|
+
* errorMessage: error.message,
|
|
68
|
+
* }),
|
|
69
|
+
* }, async () => {
|
|
70
|
+
* return await executeQuery('search query');
|
|
71
|
+
* });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function withAnalytics<TResult>(ctx: AnalyticsContext, eventName: string, events: AnalyticsEvents<TResult>, operation: () => Promise<TResult>): Promise<TResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Create a reusable analytics wrapper for a specific event
|
|
77
|
+
*
|
|
78
|
+
* @param eventName - Base event name
|
|
79
|
+
* @param defaultEvents - Default event properties
|
|
80
|
+
* @returns Function that wraps operations with analytics
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const trackQuery = createAnalyticsWrapper('mind.query', {
|
|
85
|
+
* started: { source: 'cli' },
|
|
86
|
+
* completed: (result) => ({ resultsCount: result.results.length }),
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* // Use multiple times
|
|
90
|
+
* const result1 = await trackQuery(ctx, { text: 'query 1' }, async () => {...});
|
|
91
|
+
* const result2 = await trackQuery(ctx, { text: 'query 2' }, async () => {...});
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
declare function createAnalyticsWrapper<TResult>(eventName: string, defaultEvents: AnalyticsEvents<TResult>): (ctx: AnalyticsContext, additionalEvents: Partial<AnalyticsEvents<TResult>>, operation: () => Promise<TResult>) => Promise<TResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Track a simple event (no wrapping)
|
|
97
|
+
*
|
|
98
|
+
* Convenience helper for tracking single events without wrapping an operation.
|
|
99
|
+
*
|
|
100
|
+
* @param ctx - Context with platform.analytics
|
|
101
|
+
* @param eventName - Event name
|
|
102
|
+
* @param properties - Event properties
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```typescript
|
|
106
|
+
* await trackEvent(ctx, 'button.clicked', { buttonId: 'submit', page: 'settings' });
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
declare function trackEvent(ctx: AnalyticsContext, eventName: string, properties?: Record<string, unknown>): Promise<void>;
|
|
110
|
+
|
|
111
|
+
export { type AnalyticsContext, type AnalyticsEvents, createAnalyticsWrapper, trackEvent, withAnalytics };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/analytics/with-analytics.ts
|
|
2
|
+
async function withAnalytics(ctx, eventName, events, operation) {
|
|
3
|
+
const analytics = ctx.platform?.analytics;
|
|
4
|
+
const startTime = Date.now();
|
|
5
|
+
if (analytics && events.started) {
|
|
6
|
+
await analytics.track(`${eventName}.started`, {
|
|
7
|
+
...events.started,
|
|
8
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const result = await operation();
|
|
13
|
+
if (analytics && events.completed) {
|
|
14
|
+
const completedProps = typeof events.completed === "function" ? events.completed(result) : events.completed;
|
|
15
|
+
await analytics.track(`${eventName}.completed`, {
|
|
16
|
+
...completedProps,
|
|
17
|
+
durationMs: Date.now() - startTime,
|
|
18
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (analytics && events.failed) {
|
|
24
|
+
const failedProps = typeof events.failed === "function" ? events.failed(error) : events.failed;
|
|
25
|
+
await analytics.track(`${eventName}.failed`, {
|
|
26
|
+
...failedProps,
|
|
27
|
+
durationMs: Date.now() - startTime,
|
|
28
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function createAnalyticsWrapper(eventName, defaultEvents) {
|
|
35
|
+
return async (ctx, additionalEvents, operation) => {
|
|
36
|
+
const mergedEvents = {
|
|
37
|
+
started: { ...defaultEvents.started, ...additionalEvents.started },
|
|
38
|
+
completed: additionalEvents.completed || defaultEvents.completed,
|
|
39
|
+
failed: additionalEvents.failed || defaultEvents.failed
|
|
40
|
+
};
|
|
41
|
+
return withAnalytics(ctx, eventName, mergedEvents, operation);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async function trackEvent(ctx, eventName, properties) {
|
|
45
|
+
const analytics = ctx.platform?.analytics;
|
|
46
|
+
if (analytics) {
|
|
47
|
+
await analytics.track(eventName, {
|
|
48
|
+
...properties,
|
|
49
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { createAnalyticsWrapper, trackEvent, withAnalytics };
|
|
55
|
+
//# sourceMappingURL=index.js.map
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/analytics/with-analytics.ts"],"names":[],"mappings":";AA2EA,eAAsB,aAAA,CACpB,GAAA,EACA,SAAA,EACA,MAAA,EACA,SAAA,EACkB;AAClB,EAAA,MAAM,SAAA,GAAY,IAAI,QAAA,EAAU,SAAA;AAChC,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAG3B,EAAA,IAAI,SAAA,IAAa,OAAO,OAAA,EAAS;AAC/B,IAAA,MAAM,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,QAAA,CAAA,EAAY;AAAA,MAC5C,GAAG,MAAA,CAAO,OAAA;AAAA,MACV,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACnC,CAAA;AAAA,EACH;AAEA,EAAA,IAAI;AAEF,IAAA,MAAM,MAAA,GAAS,MAAM,SAAA,EAAU;AAG/B,IAAA,IAAI,SAAA,IAAa,OAAO,SAAA,EAAW;AACjC,MAAA,MAAM,cAAA,GAAiB,OAAO,MAAA,CAAO,SAAA,KAAc,aAC/C,MAAA,CAAO,SAAA,CAAU,MAAM,CAAA,GACvB,MAAA,CAAO,SAAA;AAEX,MAAA,MAAM,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,UAAA,CAAA,EAAc;AAAA,QAC9C,GAAG,cAAA;AAAA,QACH,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,QACzB,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,OACnC,CAAA;AAAA,IACH;AAEA,IAAA,OAAO,MAAA;AAAA,EACT,SAAS,KAAA,EAAY;AAEnB,IAAA,IAAI,SAAA,IAAa,OAAO,MAAA,EAAQ;AAC9B,MAAA,MAAM,WAAA,GAAc,OAAO,MAAA,CAAO,MAAA,KAAW,aACzC,MAAA,CAAO,MAAA,CAAO,KAAK,CAAA,GACnB,MAAA,CAAO,MAAA;AAEX,MAAA,MAAM,SAAA,CAAU,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,OAAA,CAAA,EAAW;AAAA,QAC3C,GAAG,WAAA;AAAA,QACH,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAAA,QACzB,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,OACnC,CAAA;AAAA,IACH;AAGA,IAAA,MAAM,KAAA;AAAA,EACR;AACF;AAqBO,SAAS,sBAAA,CACd,WACA,aAAA,EACA;AACA,EAAA,OAAO,OACL,GAAA,EACA,gBAAA,EACA,SAAA,KACqB;AACrB,IAAA,MAAM,YAAA,GAAyC;AAAA,MAC7C,SAAS,EAAE,GAAG,cAAc,OAAA,EAAS,GAAG,iBAAiB,OAAA,EAAQ;AAAA,MACjE,SAAA,EAAW,gBAAA,CAAiB,SAAA,IAAa,aAAA,CAAc,SAAA;AAAA,MACvD,MAAA,EAAQ,gBAAA,CAAiB,MAAA,IAAU,aAAA,CAAc;AAAA,KACnD;AAEA,IAAA,OAAO,aAAA,CAAc,GAAA,EAAK,SAAA,EAAW,YAAA,EAAc,SAAS,CAAA;AAAA,EAC9D,CAAA;AACF;AAgBA,eAAsB,UAAA,CACpB,GAAA,EACA,SAAA,EACA,UAAA,EACe;AACf,EAAA,MAAM,SAAA,GAAY,IAAI,QAAA,EAAU,SAAA;AAEhC,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,MAAM,SAAA,CAAU,MAAM,SAAA,EAAW;AAAA,MAC/B,GAAG,UAAA;AAAA,MACH,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA;AAAY,KACnC,CAAA;AAAA,EACH;AACF","file":"index.js","sourcesContent":["/**\n * Analytics Wrapper\n *\n * Optional helper for wrapping operations with analytics events.\n * You can always use ctx.platform.analytics.track() directly - this is just convenience.\n *\n * @example\n * ```typescript\n * import { withAnalytics } from '@kb-labs/shared-command-kit';\n *\n * const result = await withAnalytics(ctx, 'mind.query', {\n * started: { queryId, text, mode },\n * completed: (result) => ({ tokensIn: result.tokensIn, tokensOut: result.tokensOut }),\n * failed: (error) => ({ errorMessage: error.message }),\n * }, async () => {\n * return await executeQuery(...);\n * });\n * ```\n */\n\nimport type { IAnalytics } from '@kb-labs/core-platform';\n\n/**\n * Context with optional analytics\n */\nexport interface AnalyticsContext {\n platform?: {\n analytics?: IAnalytics;\n };\n}\n\n/**\n * Analytics event configuration\n */\nexport interface AnalyticsEvents<TResult> {\n /** Event properties when operation starts */\n started?: Record<string, unknown>;\n /** Event properties when operation completes (can be function of result) */\n completed?: Record<string, unknown> | ((result: TResult) => Record<string, unknown>);\n /** Event properties when operation fails (can be function of error) */\n failed?: Record<string, unknown> | ((error: Error) => Record<string, unknown>);\n}\n\n/**\n * Wrap an async operation with analytics tracking\n *\n * Automatically tracks:\n * - `{eventName}.started` when operation begins\n * - `{eventName}.completed` when operation succeeds\n * - `{eventName}.failed` when operation fails\n *\n * @param ctx - Context with platform.analytics\n * @param eventName - Base event name (e.g., 'mind.query', 'workflow.run')\n * @param events - Event properties for started/completed/failed events\n * @param operation - Async operation to execute\n * @returns Result of the operation\n *\n * @example\n * ```typescript\n * const result = await withAnalytics(ctx, 'mind.query', {\n * started: { queryId: '123', text: 'search query' },\n * completed: (result) => ({\n * tokensIn: result.tokensIn,\n * tokensOut: result.tokensOut,\n * resultsCount: result.results.length,\n * }),\n * failed: (error) => ({\n * errorCode: error.code,\n * errorMessage: error.message,\n * }),\n * }, async () => {\n * return await executeQuery('search query');\n * });\n * ```\n */\nexport async function withAnalytics<TResult>(\n ctx: AnalyticsContext,\n eventName: string,\n events: AnalyticsEvents<TResult>,\n operation: () => Promise<TResult>\n): Promise<TResult> {\n const analytics = ctx.platform?.analytics;\n const startTime = Date.now();\n\n // Track started event\n if (analytics && events.started) {\n await analytics.track(`${eventName}.started`, {\n ...events.started,\n timestamp: new Date().toISOString(),\n });\n }\n\n try {\n // Execute operation\n const result = await operation();\n\n // Track completed event\n if (analytics && events.completed) {\n const completedProps = typeof events.completed === 'function'\n ? events.completed(result)\n : events.completed;\n\n await analytics.track(`${eventName}.completed`, {\n ...completedProps,\n durationMs: Date.now() - startTime,\n timestamp: new Date().toISOString(),\n });\n }\n\n return result;\n } catch (error: any) {\n // Track failed event\n if (analytics && events.failed) {\n const failedProps = typeof events.failed === 'function'\n ? events.failed(error)\n : events.failed;\n\n await analytics.track(`${eventName}.failed`, {\n ...failedProps,\n durationMs: Date.now() - startTime,\n timestamp: new Date().toISOString(),\n });\n }\n\n // Re-throw error\n throw error;\n }\n}\n\n/**\n * Create a reusable analytics wrapper for a specific event\n *\n * @param eventName - Base event name\n * @param defaultEvents - Default event properties\n * @returns Function that wraps operations with analytics\n *\n * @example\n * ```typescript\n * const trackQuery = createAnalyticsWrapper('mind.query', {\n * started: { source: 'cli' },\n * completed: (result) => ({ resultsCount: result.results.length }),\n * });\n *\n * // Use multiple times\n * const result1 = await trackQuery(ctx, { text: 'query 1' }, async () => {...});\n * const result2 = await trackQuery(ctx, { text: 'query 2' }, async () => {...});\n * ```\n */\nexport function createAnalyticsWrapper<TResult>(\n eventName: string,\n defaultEvents: AnalyticsEvents<TResult>\n) {\n return async (\n ctx: AnalyticsContext,\n additionalEvents: Partial<AnalyticsEvents<TResult>>,\n operation: () => Promise<TResult>\n ): Promise<TResult> => {\n const mergedEvents: AnalyticsEvents<TResult> = {\n started: { ...defaultEvents.started, ...additionalEvents.started },\n completed: additionalEvents.completed || defaultEvents.completed,\n failed: additionalEvents.failed || defaultEvents.failed,\n };\n\n return withAnalytics(ctx, eventName, mergedEvents, operation);\n };\n}\n\n/**\n * Track a simple event (no wrapping)\n *\n * Convenience helper for tracking single events without wrapping an operation.\n *\n * @param ctx - Context with platform.analytics\n * @param eventName - Event name\n * @param properties - Event properties\n *\n * @example\n * ```typescript\n * await trackEvent(ctx, 'button.clicked', { buttonId: 'submit', page: 'settings' });\n * ```\n */\nexport async function trackEvent(\n ctx: AnalyticsContext,\n eventName: string,\n properties?: Record<string, unknown>\n): Promise<void> {\n const analytics = ctx.platform?.analytics;\n\n if (analytics) {\n await analytics.track(eventName, {\n ...properties,\n timestamp: new Date().toISOString(),\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-command-kit/errors/types
|
|
3
|
+
* Types for error formatting
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Formatted error result
|
|
7
|
+
*/
|
|
8
|
+
interface FormattedError {
|
|
9
|
+
/** Human-readable error message */
|
|
10
|
+
message: string;
|
|
11
|
+
/** JSON representation */
|
|
12
|
+
json: {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: string;
|
|
15
|
+
timingMs?: number;
|
|
16
|
+
stack?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Error formatting options
|
|
21
|
+
*/
|
|
22
|
+
interface FormatErrorOptions {
|
|
23
|
+
/** Output in JSON format */
|
|
24
|
+
jsonMode?: boolean;
|
|
25
|
+
/** Include stack trace */
|
|
26
|
+
showStack?: boolean;
|
|
27
|
+
/** Timing information */
|
|
28
|
+
timingMs?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @module @kb-labs/shared-command-kit/errors/format
|
|
33
|
+
* Error formatting utilities
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Format error for display
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const formatted = formatError(error, {
|
|
42
|
+
* jsonMode: Boolean(flags.json),
|
|
43
|
+
* showStack: Boolean(flags.debug),
|
|
44
|
+
* timingMs: tracker.total(),
|
|
45
|
+
* });
|
|
46
|
+
*
|
|
47
|
+
* if (flags.json) {
|
|
48
|
+
* ctx.output?.json(formatted.json);
|
|
49
|
+
* } else {
|
|
50
|
+
* ctx.output?.error(formatted.message);
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
declare function formatError(error: unknown, options?: FormatErrorOptions): FormattedError;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Error Factory for KB Labs Plugins
|
|
58
|
+
*
|
|
59
|
+
* Optional helper for defining plugin errors without boilerplate.
|
|
60
|
+
* You can always use standard Error classes - this is just convenience.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* import { defineError } from '@kb-labs/shared-command-kit';
|
|
65
|
+
*
|
|
66
|
+
* export const MindError = defineError('MIND', {
|
|
67
|
+
* ValidationFailed: { code: 400, message: 'Validation failed' },
|
|
68
|
+
* IndexNotFound: { code: 404, message: (scope: string) => `Index '${scope}' not found` },
|
|
69
|
+
* QueryFailed: { code: 500, message: 'Query execution failed' },
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Usage:
|
|
73
|
+
* throw new MindError.IndexNotFound('default');
|
|
74
|
+
* throw new MindError.ValidationFailed({ details: { field: 'cwd' } });
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
/**
|
|
78
|
+
* Error definition with HTTP code and message
|
|
79
|
+
*/
|
|
80
|
+
interface ErrorDefinition {
|
|
81
|
+
/** HTTP status code (400, 404, 500, etc.) */
|
|
82
|
+
code: number;
|
|
83
|
+
/** Error message - can be string or function for parameterized messages */
|
|
84
|
+
message: string | ((...args: any[]) => string);
|
|
85
|
+
/** Optional additional details */
|
|
86
|
+
details?: Record<string, unknown>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Error definitions map
|
|
90
|
+
*/
|
|
91
|
+
type ErrorDefinitions = Record<string, ErrorDefinition>;
|
|
92
|
+
/**
|
|
93
|
+
* Base error class with HTTP status code support
|
|
94
|
+
*/
|
|
95
|
+
declare class PluginError extends Error {
|
|
96
|
+
readonly statusCode: number;
|
|
97
|
+
readonly errorCode: string;
|
|
98
|
+
readonly details?: Record<string, unknown>;
|
|
99
|
+
constructor(errorCode: string, message: string, statusCode: number, details?: Record<string, unknown>);
|
|
100
|
+
/**
|
|
101
|
+
* Convert error to JSON for logging/serialization
|
|
102
|
+
*/
|
|
103
|
+
toJSON(): {
|
|
104
|
+
name: string;
|
|
105
|
+
errorCode: string;
|
|
106
|
+
message: string;
|
|
107
|
+
statusCode: number;
|
|
108
|
+
details: Record<string, unknown> | undefined;
|
|
109
|
+
stack: string | undefined;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Check if error is a PluginError
|
|
113
|
+
*/
|
|
114
|
+
static isPluginError(error: unknown): error is PluginError;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Type for error constructor created by defineError
|
|
118
|
+
*/
|
|
119
|
+
type ErrorConstructor<TArgs extends any[] = any[]> = {
|
|
120
|
+
new (details?: Record<string, unknown>): PluginError;
|
|
121
|
+
new (...args: TArgs): PluginError;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Type for error namespace created by defineError
|
|
125
|
+
*/
|
|
126
|
+
type ErrorNamespace<TDefs extends ErrorDefinitions> = {
|
|
127
|
+
[K in keyof TDefs]: ErrorConstructor;
|
|
128
|
+
} & {
|
|
129
|
+
/** Check if error is from this namespace */
|
|
130
|
+
is(error: unknown): error is PluginError;
|
|
131
|
+
/** Check if error has specific error code */
|
|
132
|
+
hasCode(error: unknown, code: keyof TDefs): boolean;
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Define a namespace of plugin errors
|
|
136
|
+
*
|
|
137
|
+
* @param prefix - Error code prefix (e.g., 'MIND', 'WORKFLOW')
|
|
138
|
+
* @param definitions - Error definitions map
|
|
139
|
+
* @returns Error namespace with error constructors
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* export const MindError = defineError('MIND', {
|
|
144
|
+
* IndexNotFound: {
|
|
145
|
+
* code: 404,
|
|
146
|
+
* message: (scope: string) => `Index '${scope}' not found`
|
|
147
|
+
* },
|
|
148
|
+
* QueryFailed: {
|
|
149
|
+
* code: 500,
|
|
150
|
+
* message: 'Query execution failed'
|
|
151
|
+
* },
|
|
152
|
+
* });
|
|
153
|
+
*
|
|
154
|
+
* // Throw with template params
|
|
155
|
+
* throw new MindError.IndexNotFound('default');
|
|
156
|
+
* // Error message: "Index 'default' not found"
|
|
157
|
+
*
|
|
158
|
+
* // Throw with details
|
|
159
|
+
* throw new MindError.QueryFailed({
|
|
160
|
+
* details: { query: 'test', reason: 'timeout' }
|
|
161
|
+
* });
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
declare function defineError<TDefs extends ErrorDefinitions>(prefix: string, definitions: TDefs): ErrorNamespace<TDefs>;
|
|
165
|
+
/**
|
|
166
|
+
* Common error definitions that can be reused across plugins
|
|
167
|
+
*/
|
|
168
|
+
declare const commonErrors: {
|
|
169
|
+
/**
|
|
170
|
+
* Validation error (400)
|
|
171
|
+
*/
|
|
172
|
+
ValidationFailed: {
|
|
173
|
+
code: number;
|
|
174
|
+
message: string;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Resource not found (404)
|
|
178
|
+
*/
|
|
179
|
+
NotFound: {
|
|
180
|
+
code: number;
|
|
181
|
+
message: (resource: string) => string;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Unauthorized access (401)
|
|
185
|
+
*/
|
|
186
|
+
Unauthorized: {
|
|
187
|
+
code: number;
|
|
188
|
+
message: string;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Forbidden access (403)
|
|
192
|
+
*/
|
|
193
|
+
Forbidden: {
|
|
194
|
+
code: number;
|
|
195
|
+
message: string;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Internal server error (500)
|
|
199
|
+
*/
|
|
200
|
+
InternalError: {
|
|
201
|
+
code: number;
|
|
202
|
+
message: string;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Service unavailable (503)
|
|
206
|
+
*/
|
|
207
|
+
ServiceUnavailable: {
|
|
208
|
+
code: number;
|
|
209
|
+
message: (service: string) => string;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Timeout error (504)
|
|
213
|
+
*/
|
|
214
|
+
Timeout: {
|
|
215
|
+
code: number;
|
|
216
|
+
message: (operation: string) => string;
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Conflict error (409)
|
|
220
|
+
*/
|
|
221
|
+
Conflict: {
|
|
222
|
+
code: number;
|
|
223
|
+
message: (resource: string) => string;
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export { type ErrorDefinition, type ErrorDefinitions, type FormatErrorOptions, type FormattedError, PluginError, commonErrors, defineError, formatError };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// src/flags/types.ts
|
|
2
|
+
var FlagValidationError = class extends Error {
|
|
3
|
+
constructor(flag, message, value, schema, commandName) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.flag = flag;
|
|
6
|
+
this.value = value;
|
|
7
|
+
this.schema = schema;
|
|
8
|
+
this.commandName = commandName;
|
|
9
|
+
this.name = "FlagValidationError";
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/errors/format-validation.ts
|
|
14
|
+
function formatValidationError(error, options = {}) {
|
|
15
|
+
const { commandName, schema } = options;
|
|
16
|
+
const lines = [];
|
|
17
|
+
const errorMsg = error.message;
|
|
18
|
+
if (errorMsg.includes("is required")) {
|
|
19
|
+
lines.push(`\u274C Missing required flag: --${error.flag}`);
|
|
20
|
+
} else if (errorMsg.includes("must be one of")) {
|
|
21
|
+
const valueStr = error.value !== void 0 ? ` ${error.value}` : "";
|
|
22
|
+
lines.push(`\u274C Invalid value for --${error.flag}:${valueStr}`);
|
|
23
|
+
} else if (errorMsg.includes("must be a")) {
|
|
24
|
+
lines.push(`\u274C Invalid type for --${error.flag}`);
|
|
25
|
+
} else if (errorMsg.includes("conflicts with")) {
|
|
26
|
+
lines.push(`\u274C Flag conflict: --${error.flag}`);
|
|
27
|
+
} else if (errorMsg.includes("depends on")) {
|
|
28
|
+
lines.push(`\u274C Missing dependency for --${error.flag}`);
|
|
29
|
+
} else {
|
|
30
|
+
lines.push(`\u274C ${errorMsg}`);
|
|
31
|
+
}
|
|
32
|
+
lines.push("");
|
|
33
|
+
if (commandName && schema) {
|
|
34
|
+
const usageLine = generateUsageLine(commandName, schema, error.flag);
|
|
35
|
+
if (usageLine) {
|
|
36
|
+
lines.push(`Usage: ${usageLine}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (commandName) {
|
|
40
|
+
lines.push(`Hint: Try ${commandName} --help`);
|
|
41
|
+
} else {
|
|
42
|
+
lines.push("Hint: Try --help for more information");
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
function generateUsageLine(commandName, schema, errorFlag) {
|
|
47
|
+
const parts = [commandName];
|
|
48
|
+
const errorFlagSchema = schema[errorFlag];
|
|
49
|
+
if (errorFlagSchema) {
|
|
50
|
+
const flagStr = formatFlagForUsage(errorFlag, errorFlagSchema);
|
|
51
|
+
parts.push(flagStr);
|
|
52
|
+
}
|
|
53
|
+
const otherRequiredFlags = Object.entries(schema).filter(
|
|
54
|
+
([name, flagSchema]) => name !== errorFlag && flagSchema.required
|
|
55
|
+
);
|
|
56
|
+
if (otherRequiredFlags.length > 0) {
|
|
57
|
+
for (const [name, flagSchema] of otherRequiredFlags) {
|
|
58
|
+
parts.push(formatFlagForUsage(name, flagSchema));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const hasOptionalFlags = Object.values(schema).some((s) => !s.required);
|
|
62
|
+
if (hasOptionalFlags) {
|
|
63
|
+
parts.push("[options]");
|
|
64
|
+
}
|
|
65
|
+
return parts.join(" ");
|
|
66
|
+
}
|
|
67
|
+
function formatFlagForUsage(name, schema) {
|
|
68
|
+
let valueHint;
|
|
69
|
+
if ("choices" in schema && schema.choices && schema.choices.length > 0) {
|
|
70
|
+
valueHint = `<${schema.choices.join("|")}>`;
|
|
71
|
+
} else if (schema.type === "boolean") {
|
|
72
|
+
return schema.required ? `--${name}` : `[--${name}]`;
|
|
73
|
+
} else {
|
|
74
|
+
valueHint = `<${name}>`;
|
|
75
|
+
}
|
|
76
|
+
const flagPart = `--${name} ${valueHint}`;
|
|
77
|
+
return schema.required ? flagPart : `[${flagPart}]`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/errors/format.ts
|
|
81
|
+
function formatError(error, options = {}) {
|
|
82
|
+
const { showStack = false, timingMs } = options;
|
|
83
|
+
if (error instanceof FlagValidationError) {
|
|
84
|
+
const friendlyMessage = formatValidationError(error, {
|
|
85
|
+
commandName: error.commandName,
|
|
86
|
+
schema: error.schema
|
|
87
|
+
});
|
|
88
|
+
const json2 = {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: error.message
|
|
91
|
+
};
|
|
92
|
+
if (timingMs !== void 0) {
|
|
93
|
+
json2.timingMs = timingMs;
|
|
94
|
+
}
|
|
95
|
+
if (showStack && error.stack) {
|
|
96
|
+
json2.stack = error.stack;
|
|
97
|
+
}
|
|
98
|
+
let message2 = friendlyMessage;
|
|
99
|
+
if (showStack && error.stack) {
|
|
100
|
+
message2 = `${friendlyMessage}
|
|
101
|
+
|
|
102
|
+
Stack trace:
|
|
103
|
+
${error.stack}`;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
message: message2,
|
|
107
|
+
json: json2
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
111
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
112
|
+
const json = {
|
|
113
|
+
ok: false,
|
|
114
|
+
error: errorMessage
|
|
115
|
+
};
|
|
116
|
+
if (timingMs !== void 0) {
|
|
117
|
+
json.timingMs = timingMs;
|
|
118
|
+
}
|
|
119
|
+
if (showStack && errorStack) {
|
|
120
|
+
json.stack = errorStack;
|
|
121
|
+
}
|
|
122
|
+
let message = errorMessage;
|
|
123
|
+
if (showStack && errorStack) {
|
|
124
|
+
message = `${errorMessage}
|
|
125
|
+
|
|
126
|
+
${errorStack}`;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
message,
|
|
130
|
+
json
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/errors/factory.ts
|
|
135
|
+
var PluginError = class _PluginError extends Error {
|
|
136
|
+
statusCode;
|
|
137
|
+
errorCode;
|
|
138
|
+
details;
|
|
139
|
+
constructor(errorCode, message, statusCode, details) {
|
|
140
|
+
super(message);
|
|
141
|
+
this.name = "PluginError";
|
|
142
|
+
this.errorCode = errorCode;
|
|
143
|
+
this.statusCode = statusCode;
|
|
144
|
+
this.details = details;
|
|
145
|
+
if (Error.captureStackTrace) {
|
|
146
|
+
Error.captureStackTrace(this, _PluginError);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Convert error to JSON for logging/serialization
|
|
151
|
+
*/
|
|
152
|
+
toJSON() {
|
|
153
|
+
return {
|
|
154
|
+
name: this.name,
|
|
155
|
+
errorCode: this.errorCode,
|
|
156
|
+
message: this.message,
|
|
157
|
+
statusCode: this.statusCode,
|
|
158
|
+
details: this.details,
|
|
159
|
+
stack: this.stack
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Check if error is a PluginError
|
|
164
|
+
*/
|
|
165
|
+
static isPluginError(error) {
|
|
166
|
+
return error instanceof _PluginError;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
function defineError(prefix, definitions) {
|
|
170
|
+
const errorNamespace = {};
|
|
171
|
+
for (const [key, def] of Object.entries(definitions)) {
|
|
172
|
+
const errorCode = `${prefix}_${key.toUpperCase()}`;
|
|
173
|
+
class DefinedError extends PluginError {
|
|
174
|
+
constructor(...args) {
|
|
175
|
+
const { message, details } = buildMessageAndDetails(def, args);
|
|
176
|
+
super(errorCode, message, def.code, details);
|
|
177
|
+
this.name = `${prefix}Error`;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
errorNamespace[key] = DefinedError;
|
|
181
|
+
}
|
|
182
|
+
errorNamespace.is = (error) => {
|
|
183
|
+
return PluginError.isPluginError(error) && error.errorCode.startsWith(prefix + "_");
|
|
184
|
+
};
|
|
185
|
+
errorNamespace.hasCode = (error, code) => {
|
|
186
|
+
const errorCode = `${prefix}_${String(code).toUpperCase()}`;
|
|
187
|
+
return PluginError.isPluginError(error) && error.errorCode === errorCode;
|
|
188
|
+
};
|
|
189
|
+
return errorNamespace;
|
|
190
|
+
}
|
|
191
|
+
function buildMessageAndDetails(def, args) {
|
|
192
|
+
let message;
|
|
193
|
+
let details;
|
|
194
|
+
if (typeof def.message === "function") {
|
|
195
|
+
const lastArg = args[args.length - 1];
|
|
196
|
+
const hasDetails = lastArg && typeof lastArg === "object" && "details" in lastArg;
|
|
197
|
+
if (hasDetails) {
|
|
198
|
+
const templateArgs = args.slice(0, -1);
|
|
199
|
+
message = def.message(...templateArgs);
|
|
200
|
+
details = { ...def.details, ...lastArg.details };
|
|
201
|
+
} else {
|
|
202
|
+
message = def.message(...args);
|
|
203
|
+
details = def.details;
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
message = def.message;
|
|
207
|
+
if (args.length > 0 && typeof args[0] === "object" && args[0] !== null) {
|
|
208
|
+
details = { ...def.details, ...args[0].details };
|
|
209
|
+
} else {
|
|
210
|
+
details = def.details;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return { message, details };
|
|
214
|
+
}
|
|
215
|
+
var commonErrors = {
|
|
216
|
+
/**
|
|
217
|
+
* Validation error (400)
|
|
218
|
+
*/
|
|
219
|
+
ValidationFailed: {
|
|
220
|
+
code: 400,
|
|
221
|
+
message: "Validation failed"
|
|
222
|
+
},
|
|
223
|
+
/**
|
|
224
|
+
* Resource not found (404)
|
|
225
|
+
*/
|
|
226
|
+
NotFound: {
|
|
227
|
+
code: 404,
|
|
228
|
+
message: (resource) => `${resource} not found`
|
|
229
|
+
},
|
|
230
|
+
/**
|
|
231
|
+
* Unauthorized access (401)
|
|
232
|
+
*/
|
|
233
|
+
Unauthorized: {
|
|
234
|
+
code: 401,
|
|
235
|
+
message: "Unauthorized"
|
|
236
|
+
},
|
|
237
|
+
/**
|
|
238
|
+
* Forbidden access (403)
|
|
239
|
+
*/
|
|
240
|
+
Forbidden: {
|
|
241
|
+
code: 403,
|
|
242
|
+
message: "Forbidden"
|
|
243
|
+
},
|
|
244
|
+
/**
|
|
245
|
+
* Internal server error (500)
|
|
246
|
+
*/
|
|
247
|
+
InternalError: {
|
|
248
|
+
code: 500,
|
|
249
|
+
message: "Internal server error"
|
|
250
|
+
},
|
|
251
|
+
/**
|
|
252
|
+
* Service unavailable (503)
|
|
253
|
+
*/
|
|
254
|
+
ServiceUnavailable: {
|
|
255
|
+
code: 503,
|
|
256
|
+
message: (service) => `Service '${service}' is unavailable`
|
|
257
|
+
},
|
|
258
|
+
/**
|
|
259
|
+
* Timeout error (504)
|
|
260
|
+
*/
|
|
261
|
+
Timeout: {
|
|
262
|
+
code: 504,
|
|
263
|
+
message: (operation) => `Operation '${operation}' timed out`
|
|
264
|
+
},
|
|
265
|
+
/**
|
|
266
|
+
* Conflict error (409)
|
|
267
|
+
*/
|
|
268
|
+
Conflict: {
|
|
269
|
+
code: 409,
|
|
270
|
+
message: (resource) => `${resource} already exists`
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export { PluginError, commonErrors, defineError, formatError };
|
|
275
|
+
//# sourceMappingURL=index.js.map
|
|
276
|
+
//# sourceMappingURL=index.js.map
|