@kb-labs/shared 1.1.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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Define a CLI command handler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PluginContextV3, CommandResult, HostContext } from '@kb-labs/plugin-contracts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CLI input wrapper - V3 plugin system wraps flags in this structure
|
|
9
|
+
*/
|
|
10
|
+
export interface CLIInput<TFlags = unknown> {
|
|
11
|
+
flags: TFlags;
|
|
12
|
+
argv: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CommandHandlerV3<TConfig = unknown, TInput = unknown, TResult = unknown> {
|
|
16
|
+
/**
|
|
17
|
+
* Execute the command
|
|
18
|
+
*/
|
|
19
|
+
execute(
|
|
20
|
+
context: PluginContextV3<TConfig>,
|
|
21
|
+
input: TInput
|
|
22
|
+
): Promise<CommandResult<TResult>> | CommandResult<TResult>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional cleanup - called after execute completes
|
|
26
|
+
*/
|
|
27
|
+
cleanup?(): Promise<void> | void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CommandDefinition<TConfig = unknown, TInput = unknown, TResult = unknown> {
|
|
31
|
+
/**
|
|
32
|
+
* Command ID (e.g., "my-plugin:greet")
|
|
33
|
+
*/
|
|
34
|
+
id: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Command description
|
|
38
|
+
*/
|
|
39
|
+
description?: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Handler implementation
|
|
43
|
+
*/
|
|
44
|
+
handler: CommandHandlerV3<TConfig, TInput, TResult>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Optional input schema validation (future: use Zod/JSON Schema)
|
|
48
|
+
*/
|
|
49
|
+
schema?: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Define a CLI command
|
|
54
|
+
*
|
|
55
|
+
* For CLI commands, use CLIInput<TFlags> as TInput type to get proper typing
|
|
56
|
+
* for the V3 plugin system's { flags, argv } structure.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* interface GreetFlags {
|
|
61
|
+
* name?: string;
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* interface GreetResult {
|
|
65
|
+
* message: string;
|
|
66
|
+
* target: string;
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* export default defineCommand<unknown, CLIInput<GreetFlags>, GreetResult>({
|
|
70
|
+
* id: 'greet',
|
|
71
|
+
* description: 'Greet a user',
|
|
72
|
+
* handler: {
|
|
73
|
+
* async execute(context, input) {
|
|
74
|
+
* const target = input.flags.name || 'World';
|
|
75
|
+
* context.ui.success(`Hello, ${target}!`);
|
|
76
|
+
*
|
|
77
|
+
* return {
|
|
78
|
+
* exitCode: 0,
|
|
79
|
+
* result: { message: `Hello, ${target}!`, target },
|
|
80
|
+
* meta: { version: 'v3' }
|
|
81
|
+
* };
|
|
82
|
+
* }
|
|
83
|
+
* }
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function defineCommand<TConfig = unknown, TInput = unknown, TResult = unknown>(
|
|
88
|
+
definition: CommandDefinition<TConfig, TInput, TResult>
|
|
89
|
+
): CommandHandlerV3<TConfig, TInput, TResult> {
|
|
90
|
+
// Return the handler directly with host validation wrapper
|
|
91
|
+
return {
|
|
92
|
+
execute: (context, input) => {
|
|
93
|
+
// Ensure we're running in CLI or workflow host
|
|
94
|
+
if (context.host !== 'cli' && context.host !== 'workflow') {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Command ${definition.id} can only run in CLI or workflow host (current: ${context.host})`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Call the actual handler
|
|
101
|
+
return definition.handler.execute(context, input);
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
cleanup: definition.handler.cleanup,
|
|
105
|
+
} as CommandHandlerV3<TConfig, TInput, TResult>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Type guard to check if host context is CLI
|
|
110
|
+
*/
|
|
111
|
+
export function isCLIHost(hostContext: HostContext): hostContext is Extract<HostContext, { host: 'cli' }> {
|
|
112
|
+
return hostContext.host === 'cli';
|
|
113
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Define a REST API route handler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PluginContextV3, CommandResult, HostContext } from '@kb-labs/plugin-contracts';
|
|
6
|
+
|
|
7
|
+
export interface RouteHandler<TConfig = unknown, TInput = unknown> {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the route handler
|
|
10
|
+
*/
|
|
11
|
+
execute(
|
|
12
|
+
context: PluginContextV3<TConfig>,
|
|
13
|
+
input: TInput
|
|
14
|
+
): Promise<CommandResult | void> | CommandResult | void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Optional cleanup - called after execute completes
|
|
18
|
+
*/
|
|
19
|
+
cleanup?(): Promise<void> | void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RouteDefinition<TConfig = unknown, TInput = unknown> {
|
|
23
|
+
/**
|
|
24
|
+
* Route path (e.g., "/api/greet")
|
|
25
|
+
*/
|
|
26
|
+
path: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* HTTP method (e.g., "GET", "POST")
|
|
30
|
+
*/
|
|
31
|
+
method: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Route description
|
|
35
|
+
*/
|
|
36
|
+
description?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Handler implementation
|
|
40
|
+
*/
|
|
41
|
+
handler: RouteHandler<TConfig, TInput>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional input schema validation (future: use Zod/JSON Schema)
|
|
45
|
+
*/
|
|
46
|
+
schema?: unknown;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Define a REST API route
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* export default defineRoute({
|
|
55
|
+
* path: '/greet',
|
|
56
|
+
* method: 'POST',
|
|
57
|
+
* description: 'Greet a user',
|
|
58
|
+
* handler: {
|
|
59
|
+
* async execute(context, input: { name: string }) {
|
|
60
|
+
* return {
|
|
61
|
+
* data: { message: `Hello, ${input.name}!` },
|
|
62
|
+
* exitCode: 0,
|
|
63
|
+
* };
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function defineRoute<TConfig = unknown, TInput = unknown>(
|
|
70
|
+
definition: RouteDefinition<TConfig, TInput>
|
|
71
|
+
): RouteHandler<TConfig, TInput> {
|
|
72
|
+
// Validate host type at runtime
|
|
73
|
+
const wrappedHandler: RouteHandler<TConfig, TInput> = {
|
|
74
|
+
execute: async (context, input) => {
|
|
75
|
+
// Ensure we're running in REST host
|
|
76
|
+
if (context.host !== 'rest') {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Route ${definition.path} can only run in REST host (current: ${context.host})`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate method if provided in host context
|
|
83
|
+
if (isRESTHost(context.hostContext)) {
|
|
84
|
+
const expectedMethod = definition.method.toUpperCase();
|
|
85
|
+
const actualMethod = context.hostContext.method.toUpperCase();
|
|
86
|
+
if (actualMethod !== expectedMethod) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Route ${definition.path} expects ${expectedMethod} but got ${actualMethod}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Call the actual handler with error handling
|
|
94
|
+
try {
|
|
95
|
+
return await definition.handler.execute(context, input);
|
|
96
|
+
} finally {
|
|
97
|
+
// Always call cleanup, even if handler throws
|
|
98
|
+
await definition.handler.cleanup?.();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
cleanup: definition.handler.cleanup,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return wrappedHandler;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Type guard to check if host context is REST
|
|
110
|
+
*/
|
|
111
|
+
export function isRESTHost(hostContext: HostContext): hostContext is Extract<HostContext, { host: 'rest' }> {
|
|
112
|
+
return hostContext.host === 'rest';
|
|
113
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-command-kit
|
|
3
|
+
* System Command Definition - For KB Labs official commands with full privileges
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type CommandResult,
|
|
8
|
+
type FlagSchemaDefinition,
|
|
9
|
+
type InferFlags,
|
|
10
|
+
defineFlags,
|
|
11
|
+
validateFlags,
|
|
12
|
+
FlagValidationError,
|
|
13
|
+
} from './index';
|
|
14
|
+
import type { PluginContextV3 } from '@kb-labs/plugin-contracts';
|
|
15
|
+
import { formatError } from './errors/index';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Flag definition for Command interface
|
|
19
|
+
*/
|
|
20
|
+
interface FlagDefinition {
|
|
21
|
+
name: string;
|
|
22
|
+
type: 'string' | 'boolean' | 'number' | 'array';
|
|
23
|
+
alias?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
default?: unknown;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
choices?: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Command definition for system commands
|
|
32
|
+
*/
|
|
33
|
+
export interface Command {
|
|
34
|
+
name: string;
|
|
35
|
+
describe: string;
|
|
36
|
+
longDescription?: string;
|
|
37
|
+
category?: string;
|
|
38
|
+
aliases?: string[];
|
|
39
|
+
flags?: FlagDefinition[];
|
|
40
|
+
examples?: string[];
|
|
41
|
+
run: (ctx: PluginContextV3, argv: string[], flags: Record<string, unknown>) => Promise<number>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Command group definition
|
|
46
|
+
*/
|
|
47
|
+
export interface CommandGroup {
|
|
48
|
+
name: string;
|
|
49
|
+
describe: string;
|
|
50
|
+
commands: Command[];
|
|
51
|
+
subgroups?: CommandGroup[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert flag schema definition to FlagDefinition[] format
|
|
56
|
+
* Used for compatibility with Command interface
|
|
57
|
+
*/
|
|
58
|
+
function convertFlagSchema(schema: FlagSchemaDefinition): FlagDefinition[] {
|
|
59
|
+
return Object.entries(schema).map(([name, def]) => {
|
|
60
|
+
const result: FlagDefinition = {
|
|
61
|
+
name,
|
|
62
|
+
type: def.type,
|
|
63
|
+
alias: def.alias,
|
|
64
|
+
description: def.description,
|
|
65
|
+
default: def.default,
|
|
66
|
+
required: def.required,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Add choices if present (for string flags with choices)
|
|
70
|
+
if (def.type === 'string' && 'choices' in def && Array.isArray(def.choices)) {
|
|
71
|
+
result.choices = [...def.choices];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Extended command config for system commands
|
|
80
|
+
*
|
|
81
|
+
* All type parameters are optional - use them when you want type safety, skip them for simplicity.
|
|
82
|
+
*
|
|
83
|
+
* TFlags can be inferred from the flags schema, but can also be explicitly provided
|
|
84
|
+
* for better type inference in complex cases.
|
|
85
|
+
*
|
|
86
|
+
* TResult must extend CommandResult (requires ok: boolean field).
|
|
87
|
+
*/
|
|
88
|
+
export interface SystemCommandConfig<
|
|
89
|
+
TFlags extends FlagSchemaDefinition = FlagSchemaDefinition,
|
|
90
|
+
TResult extends CommandResult = CommandResult,
|
|
91
|
+
TArgv extends readonly string[] = string[]
|
|
92
|
+
> {
|
|
93
|
+
/** Command name (required for system commands) */
|
|
94
|
+
name: string;
|
|
95
|
+
/** Command description (required for system commands) */
|
|
96
|
+
description: string;
|
|
97
|
+
/** Long description for help */
|
|
98
|
+
longDescription?: string;
|
|
99
|
+
/** Category (defaults to 'system') */
|
|
100
|
+
category?: string;
|
|
101
|
+
/** Command aliases */
|
|
102
|
+
aliases?: string[];
|
|
103
|
+
/** Usage examples */
|
|
104
|
+
examples?: string[];
|
|
105
|
+
/** Flag schema definition - TypeScript will infer TFlags from this */
|
|
106
|
+
flags: TFlags;
|
|
107
|
+
/**
|
|
108
|
+
* Analytics configuration (legacy field, kept for backward compatibility)
|
|
109
|
+
* Use ctx.platform.analytics.track() or withAnalytics() helper instead
|
|
110
|
+
*/
|
|
111
|
+
analytics?: {
|
|
112
|
+
command?: string;
|
|
113
|
+
startEvent?: string;
|
|
114
|
+
finishEvent?: string;
|
|
115
|
+
actor?: string;
|
|
116
|
+
context?: Record<string, unknown>;
|
|
117
|
+
includeFlags?: boolean;
|
|
118
|
+
};
|
|
119
|
+
/** Command handler - receives PluginContextV3 and must return TResult */
|
|
120
|
+
handler: (ctx: PluginContextV3, argv: TArgv, flags: InferFlags<TFlags>) => Promise<number | TResult> | number | TResult;
|
|
121
|
+
/** Optional formatter - receives TResult with inferred flags */
|
|
122
|
+
formatter?: (result: TResult, ctx: PluginContextV3, flags: InferFlags<TFlags>, argv?: TArgv) => void;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Define a system command with full privileges
|
|
127
|
+
*
|
|
128
|
+
* System commands are KB Labs official commands that run with full system access,
|
|
129
|
+
* bypassing sandbox restrictions. They are architecturally separated from plugin
|
|
130
|
+
* commands - plugins can NEVER gain system privileges.
|
|
131
|
+
*
|
|
132
|
+
* TResult is REQUIRED - every command must explicitly declare its result contract.
|
|
133
|
+
* This ensures type safety and enables future contract validation.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* import { defineSystemCommand, type CommandResult, type FlagSchemaDefinition } from '@kb-labs/shared-command-kit';
|
|
138
|
+
*
|
|
139
|
+
* // Simple command with automatic flag type inference (RECOMMENDED)
|
|
140
|
+
* export const helloCommand = defineSystemCommand<CommandResult & { message: string }>({
|
|
141
|
+
* name: 'hello',
|
|
142
|
+
* description: 'Print a friendly greeting',
|
|
143
|
+
* category: 'system',
|
|
144
|
+
* flags: {
|
|
145
|
+
* name: { type: 'string', description: 'Name to greet' },
|
|
146
|
+
* json: { type: 'boolean', default: false },
|
|
147
|
+
* } satisfies FlagSchemaDefinition, // Preserves literal types for type inference
|
|
148
|
+
* async handler(ctx, argv, flags) {
|
|
149
|
+
* // ctx is EnhancedCliContext (extends PluginContextV3)
|
|
150
|
+
* // ctx.cwd - working directory (V2 promoted field)
|
|
151
|
+
* // ctx.ui - UI output API with new convenience methods
|
|
152
|
+
* // flags.name is inferred as string | undefined
|
|
153
|
+
* const message = `Hello, ${flags.name || 'World'}!`;
|
|
154
|
+
*
|
|
155
|
+
* // UI output (CLI only, no-op in REST/Workflow)
|
|
156
|
+
* if (!flags.json) {
|
|
157
|
+
* if (ctx.ui?.success) {
|
|
158
|
+
* ctx.ui.success('Greeting', [
|
|
159
|
+
* { items: [message] },
|
|
160
|
+
* ]);
|
|
161
|
+
* }
|
|
162
|
+
* } else {
|
|
163
|
+
* ctx.ui?.json({ message });
|
|
164
|
+
* }
|
|
165
|
+
*
|
|
166
|
+
* return { ok: true, message };
|
|
167
|
+
* },
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* // Command with explicit result type and automatic flag inference
|
|
171
|
+
* type WorkflowListResult = CommandResult & {
|
|
172
|
+
* workflows: Array<{ id: string }>;
|
|
173
|
+
* total: number;
|
|
174
|
+
* };
|
|
175
|
+
*
|
|
176
|
+
* export const wfList = defineSystemCommand<WorkflowListResult>({
|
|
177
|
+
* name: 'list',
|
|
178
|
+
* description: 'List workflows',
|
|
179
|
+
* flags: {
|
|
180
|
+
* source: { type: 'string', default: 'all' },
|
|
181
|
+
* tag: { type: 'string' },
|
|
182
|
+
* json: { type: 'boolean', default: false },
|
|
183
|
+
* } satisfies FlagSchemaDefinition, // TypeScript infers flag types automatically
|
|
184
|
+
* async handler(ctx, argv, flags) {
|
|
185
|
+
* // flags.source is inferred as string
|
|
186
|
+
* // flags.tag is inferred as string | undefined
|
|
187
|
+
* // flags.json is inferred as boolean
|
|
188
|
+
* const workflows = await listWorkflows(flags.source, flags.tag);
|
|
189
|
+
*
|
|
190
|
+
* // UI output (new convenience methods)
|
|
191
|
+
* if (!flags.json) {
|
|
192
|
+
* if (ctx.ui?.success) {
|
|
193
|
+
* ctx.ui.success('Workflows', [
|
|
194
|
+
* { header: 'Summary', items: [`Total: ${workflows.length}`, `Source: ${flags.source}`] },
|
|
195
|
+
* { items: workflows.map(w => `• ${w.id}`) },
|
|
196
|
+
* ]);
|
|
197
|
+
* }
|
|
198
|
+
* } else {
|
|
199
|
+
* ctx.ui?.json({ workflows, total: workflows.length });
|
|
200
|
+
* }
|
|
201
|
+
*
|
|
202
|
+
* return { ok: true, workflows, total: workflows.length };
|
|
203
|
+
* },
|
|
204
|
+
* });
|
|
205
|
+
*
|
|
206
|
+
* // Command with explicit flag types (for complex cases)
|
|
207
|
+
* export const wfRun = defineSystemCommand<
|
|
208
|
+
* { 'workflow-id': { type: 'string'; required: true } },
|
|
209
|
+
* CommandResult & { run: WorkflowRun }
|
|
210
|
+
* >({
|
|
211
|
+
* name: 'run',
|
|
212
|
+
* description: 'Execute a workflow',
|
|
213
|
+
* flags: {
|
|
214
|
+
* 'workflow-id': { type: 'string', required: true },
|
|
215
|
+
* },
|
|
216
|
+
* async handler(ctx, argv, flags) {
|
|
217
|
+
* // flags['workflow-id'] is inferred as string (required)
|
|
218
|
+
*
|
|
219
|
+
* // Progress tracking (CLI only, no-op in REST/Workflow)
|
|
220
|
+
* ctx.ui?.startProgress('running', 'Starting workflow...');
|
|
221
|
+
* const run = await executeWorkflow(flags['workflow-id']);
|
|
222
|
+
* ctx.ui?.completeProgress('running', 'Workflow started!');
|
|
223
|
+
*
|
|
224
|
+
* return { ok: true, run };
|
|
225
|
+
* },
|
|
226
|
+
* });
|
|
227
|
+
*
|
|
228
|
+
* // Note: System commands support all UI convenience methods:
|
|
229
|
+
* // - ctx.ui.success() / showError() / warning() / info() - Result display with sideBox
|
|
230
|
+
* // - ctx.ui.startProgress() / completeProgress() / failProgress() - Progress tracking
|
|
231
|
+
* // - ctx.ui.table() / keyValue() / list() - Low-level formatting
|
|
232
|
+
* // All UI methods are CLI-only and no-op in REST/Workflow contexts
|
|
233
|
+
* ```
|
|
234
|
+
*/
|
|
235
|
+
// Overload for explicit TFlags and TResult
|
|
236
|
+
export function defineSystemCommand<
|
|
237
|
+
TFlags extends FlagSchemaDefinition,
|
|
238
|
+
TResult extends CommandResult = CommandResult,
|
|
239
|
+
TArgv extends readonly string[] = string[]
|
|
240
|
+
>(
|
|
241
|
+
config: SystemCommandConfig<TFlags, TResult, TArgv>
|
|
242
|
+
): Command;
|
|
243
|
+
// Implementation - all parameters optional with defaults
|
|
244
|
+
export function defineSystemCommand<
|
|
245
|
+
TFlags extends FlagSchemaDefinition = FlagSchemaDefinition,
|
|
246
|
+
TResult extends CommandResult = CommandResult,
|
|
247
|
+
TArgv extends readonly string[] = string[]
|
|
248
|
+
>(
|
|
249
|
+
config: SystemCommandConfig<TFlags, TResult, TArgv>
|
|
250
|
+
): Command {
|
|
251
|
+
const { name, description, longDescription, category, aliases, examples, flags, handler, formatter } = config;
|
|
252
|
+
|
|
253
|
+
// Return command with direct handler (no wrapping needed - system commands don't use V3 defineCommand)
|
|
254
|
+
return {
|
|
255
|
+
name,
|
|
256
|
+
describe: description,
|
|
257
|
+
longDescription,
|
|
258
|
+
category: category || 'system',
|
|
259
|
+
aliases: aliases || [],
|
|
260
|
+
flags: flags ? convertFlagSchema(flags) : [],
|
|
261
|
+
examples: examples || [],
|
|
262
|
+
run: async (ctx: PluginContextV3, argv: string[], rawFlags: Record<string, unknown>) => {
|
|
263
|
+
const jsonMode = Boolean(rawFlags.json);
|
|
264
|
+
|
|
265
|
+
// Log command start
|
|
266
|
+
ctx.platform?.logger?.info?.(`Command ${name} started`);
|
|
267
|
+
|
|
268
|
+
// Validate flags only if schema is defined
|
|
269
|
+
let validatedFlags: InferFlags<TFlags>;
|
|
270
|
+
|
|
271
|
+
if (flags) {
|
|
272
|
+
const flagSchema = defineFlags(flags);
|
|
273
|
+
try {
|
|
274
|
+
validatedFlags = await validateFlags(rawFlags, flagSchema) as InferFlags<TFlags>;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
// Enhance validation error with command name
|
|
277
|
+
if (error instanceof FlagValidationError && name && !error.commandName) {
|
|
278
|
+
(error as { commandName?: string }).commandName = name;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Show stack trace only in debug mode
|
|
282
|
+
const showStack = Boolean(rawFlags.debug);
|
|
283
|
+
const formatted = formatError(error, { showStack, jsonMode });
|
|
284
|
+
|
|
285
|
+
if (jsonMode) {
|
|
286
|
+
ctx.ui?.json(formatted.json);
|
|
287
|
+
} else {
|
|
288
|
+
ctx.ui?.error(formatted.message);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Return specific exit code for validation errors
|
|
292
|
+
return 3; // EXIT_CODES.INVALID_FLAGS
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
// No schema defined - pass raw flags as-is
|
|
296
|
+
validatedFlags = rawFlags as InferFlags<TFlags>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Call handler with error handling
|
|
300
|
+
let result: number | TResult;
|
|
301
|
+
try {
|
|
302
|
+
result = await handler(ctx, argv as unknown as TArgv, validatedFlags);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// Log error to platform logger
|
|
305
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
306
|
+
ctx.platform?.logger?.error?.(`Command ${name} failed: ${errorMessage}`);
|
|
307
|
+
|
|
308
|
+
// Display error to user
|
|
309
|
+
if (jsonMode) {
|
|
310
|
+
ctx.ui?.json({ ok: false, error: errorMessage });
|
|
311
|
+
} else {
|
|
312
|
+
ctx.ui?.error(errorMessage);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Return error exit code
|
|
316
|
+
return 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Call formatter if provided and result is not a number
|
|
320
|
+
if (formatter && typeof result !== 'number') {
|
|
321
|
+
formatter(result as TResult, ctx, validatedFlags, argv as unknown as TArgv);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Log command completion
|
|
325
|
+
ctx.platform?.logger?.info?.(`Command ${name} completed`);
|
|
326
|
+
|
|
327
|
+
// Convert result to exit code
|
|
328
|
+
if (typeof result === 'number') {
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
return result.ok ? 0 : 1;
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Create a system command group
|
|
338
|
+
*
|
|
339
|
+
* Groups organize related system commands for better help organization
|
|
340
|
+
* and registration.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```typescript
|
|
344
|
+
* export const systemInfoGroup = defineSystemCommandGroup(
|
|
345
|
+
* 'system:info',
|
|
346
|
+
* 'System information commands',
|
|
347
|
+
* [helloCommand, versionCommand, healthCommand]
|
|
348
|
+
* );
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
export function defineSystemCommandGroup(
|
|
352
|
+
name: string,
|
|
353
|
+
describe: string,
|
|
354
|
+
commands: Command[]
|
|
355
|
+
): CommandGroup {
|
|
356
|
+
return {
|
|
357
|
+
name,
|
|
358
|
+
describe,
|
|
359
|
+
commands,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Define a webhook handler
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PluginContextV3, CommandResult, HostContext } from '@kb-labs/plugin-contracts';
|
|
6
|
+
|
|
7
|
+
export interface WebhookHandler<TConfig = unknown, TInput = unknown> {
|
|
8
|
+
/**
|
|
9
|
+
* Execute the webhook handler
|
|
10
|
+
*/
|
|
11
|
+
execute(
|
|
12
|
+
context: PluginContextV3<TConfig>,
|
|
13
|
+
input: TInput
|
|
14
|
+
): Promise<CommandResult | void> | CommandResult | void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Optional cleanup - called after execute completes
|
|
18
|
+
*/
|
|
19
|
+
cleanup?(): Promise<void> | void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface WebhookDefinition<TConfig = unknown, TInput = unknown> {
|
|
23
|
+
/**
|
|
24
|
+
* Event name (e.g., "github:push", "stripe:payment.success")
|
|
25
|
+
*/
|
|
26
|
+
event: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Webhook description
|
|
30
|
+
*/
|
|
31
|
+
description?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Handler implementation
|
|
35
|
+
*/
|
|
36
|
+
handler: WebhookHandler<TConfig, TInput>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional input schema validation (future: use Zod/JSON Schema)
|
|
40
|
+
*/
|
|
41
|
+
schema?: unknown;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Define a webhook handler
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* export default defineWebhook({
|
|
50
|
+
* event: 'github:push',
|
|
51
|
+
* description: 'Handle GitHub push events',
|
|
52
|
+
* handler: {
|
|
53
|
+
* async execute(context, input: { ref: string; commits: any[] }) {
|
|
54
|
+
* const { event, source, payload } = context.hostContext as WebhookHost;
|
|
55
|
+
*
|
|
56
|
+
* context.ui.info(`Received ${event} from ${source}`);
|
|
57
|
+
*
|
|
58
|
+
* // Process webhook...
|
|
59
|
+
*
|
|
60
|
+
* return {
|
|
61
|
+
* data: { processed: true },
|
|
62
|
+
* exitCode: 0,
|
|
63
|
+
* };
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
export function defineWebhook<TConfig = unknown, TInput = unknown>(
|
|
70
|
+
definition: WebhookDefinition<TConfig, TInput>
|
|
71
|
+
): WebhookHandler<TConfig, TInput> {
|
|
72
|
+
// Validate host type at runtime
|
|
73
|
+
const wrappedHandler: WebhookHandler<TConfig, TInput> = {
|
|
74
|
+
execute: async (context, input) => {
|
|
75
|
+
// Ensure we're running in webhook host
|
|
76
|
+
if (context.host !== 'webhook') {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Webhook ${definition.event} can only run in webhook host (current: ${context.host})`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate event if provided in host context
|
|
83
|
+
if (isWebhookHost(context.hostContext)) {
|
|
84
|
+
const expectedEvent = definition.event;
|
|
85
|
+
const actualEvent = context.hostContext.event;
|
|
86
|
+
if (actualEvent !== expectedEvent) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Webhook expects event ${expectedEvent} but got ${actualEvent}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Call the actual handler with error handling
|
|
94
|
+
try {
|
|
95
|
+
return await definition.handler.execute(context, input);
|
|
96
|
+
} finally {
|
|
97
|
+
// Always call cleanup, even if handler throws
|
|
98
|
+
await definition.handler.cleanup?.();
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
cleanup: definition.handler.cleanup,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return wrappedHandler;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Type guard to check if host context is webhook
|
|
110
|
+
*/
|
|
111
|
+
export function isWebhookHost(
|
|
112
|
+
hostContext: HostContext
|
|
113
|
+
): hostContext is Extract<HostContext, { host: 'webhook' }> {
|
|
114
|
+
return hostContext.host === 'webhook';
|
|
115
|
+
}
|