@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.
Files changed (232) hide show
  1. package/.cursorrules +32 -0
  2. package/.github/workflows/ci.yml +13 -0
  3. package/.github/workflows/deploy.yml +28 -0
  4. package/.github/workflows/docker-build.yml +25 -0
  5. package/.github/workflows/drift-check.yml +10 -0
  6. package/.github/workflows/profiles-validate.yml +16 -0
  7. package/.github/workflows/release.yml +8 -0
  8. package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
  9. package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
  10. package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
  11. package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
  12. package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
  13. package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
  14. package/.kb/devkit/agents/release-manager/context.globs +7 -0
  15. package/.kb/devkit/agents/release-manager/prompt.md +27 -0
  16. package/.kb/devkit/agents/release-manager/runbook.md +17 -0
  17. package/.kb/devkit/agents/test-generator/context.globs +7 -0
  18. package/.kb/devkit/agents/test-generator/prompt.md +27 -0
  19. package/.kb/devkit/agents/test-generator/runbook.md +18 -0
  20. package/.vscode/settings.json +23 -0
  21. package/CHANGELOG.md +33 -0
  22. package/CONTRIBUTING.md +117 -0
  23. package/LICENSE +21 -0
  24. package/README.md +306 -0
  25. package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
  26. package/docs/DOCUMENTATION.md +70 -0
  27. package/docs/adr/0000-template.md +52 -0
  28. package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
  29. package/docs/adr/0002-plugins-and-extensibility.md +44 -0
  30. package/docs/adr/0003-package-and-module-boundaries.md +35 -0
  31. package/docs/adr/0004-versioning-and-release-policy.md +36 -0
  32. package/docs/adr/0005-reactive-loader-pattern.md +179 -0
  33. package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
  34. package/eslint.config.js +27 -0
  35. package/kb-labs.config.json +5 -0
  36. package/package.json +88 -0
  37. package/package.json.bin +25 -0
  38. package/package.json.lib +30 -0
  39. package/packages/shared-cli-ui/CHANGELOG.md +20 -0
  40. package/packages/shared-cli-ui/README.md +342 -0
  41. package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
  42. package/packages/shared-cli-ui/eslint.config.js +27 -0
  43. package/packages/shared-cli-ui/package.json +72 -0
  44. package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
  45. package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
  46. package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
  47. package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
  48. package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
  49. package/packages/shared-cli-ui/src/colors.ts +142 -0
  50. package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
  51. package/packages/shared-cli-ui/src/command-output.ts +153 -0
  52. package/packages/shared-cli-ui/src/command-result.ts +267 -0
  53. package/packages/shared-cli-ui/src/command-runner.ts +310 -0
  54. package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
  55. package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
  56. package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
  57. package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
  58. package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
  59. package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
  60. package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
  61. package/packages/shared-cli-ui/src/debug/index.ts +56 -0
  62. package/packages/shared-cli-ui/src/debug/types.ts +57 -0
  63. package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
  64. package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
  65. package/packages/shared-cli-ui/src/format.ts +412 -0
  66. package/packages/shared-cli-ui/src/index.ts +34 -0
  67. package/packages/shared-cli-ui/src/loader.ts +196 -0
  68. package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
  69. package/packages/shared-cli-ui/src/modern-format.ts +271 -0
  70. package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
  71. package/packages/shared-cli-ui/src/table.ts +134 -0
  72. package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
  73. package/packages/shared-cli-ui/src/utils/context.ts +12 -0
  74. package/packages/shared-cli-ui/src/utils/env.ts +164 -0
  75. package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
  76. package/packages/shared-cli-ui/src/utils/path.ts +8 -0
  77. package/packages/shared-cli-ui/tsconfig.build.json +15 -0
  78. package/packages/shared-cli-ui/tsconfig.json +9 -0
  79. package/packages/shared-cli-ui/tsup.config.ts +11 -0
  80. package/packages/shared-cli-ui/vitest.config.ts +15 -0
  81. package/packages/shared-command-kit/CHANGELOG.md +20 -0
  82. package/packages/shared-command-kit/LICENSE +22 -0
  83. package/packages/shared-command-kit/README.md +1030 -0
  84. package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
  85. package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
  86. package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
  87. package/packages/shared-command-kit/eslint.config.js +27 -0
  88. package/packages/shared-command-kit/eslint.config.ts +14 -0
  89. package/packages/shared-command-kit/package.json +76 -0
  90. package/packages/shared-command-kit/prettierrc.json +5 -0
  91. package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
  92. package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
  93. package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
  94. package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
  95. package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
  96. package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
  97. package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
  98. package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
  99. package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
  100. package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
  101. package/packages/shared-command-kit/src/analytics/index.ts +6 -0
  102. package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
  103. package/packages/shared-command-kit/src/define-action.ts +100 -0
  104. package/packages/shared-command-kit/src/define-command.ts +113 -0
  105. package/packages/shared-command-kit/src/define-route.ts +113 -0
  106. package/packages/shared-command-kit/src/define-system-command.ts +362 -0
  107. package/packages/shared-command-kit/src/define-webhook.ts +115 -0
  108. package/packages/shared-command-kit/src/define-websocket.ts +308 -0
  109. package/packages/shared-command-kit/src/errors/factory.ts +282 -0
  110. package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
  111. package/packages/shared-command-kit/src/errors/format.ts +92 -0
  112. package/packages/shared-command-kit/src/errors/index.ts +9 -0
  113. package/packages/shared-command-kit/src/errors/types.ts +32 -0
  114. package/packages/shared-command-kit/src/flags/define.ts +92 -0
  115. package/packages/shared-command-kit/src/flags/index.ts +9 -0
  116. package/packages/shared-command-kit/src/flags/types.ts +153 -0
  117. package/packages/shared-command-kit/src/flags/validate.ts +358 -0
  118. package/packages/shared-command-kit/src/helpers/context.ts +8 -0
  119. package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
  120. package/packages/shared-command-kit/src/helpers/index.ts +42 -0
  121. package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
  122. package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
  123. package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
  124. package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
  125. package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
  126. package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
  127. package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
  128. package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
  129. package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
  130. package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
  131. package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
  132. package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
  133. package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
  134. package/packages/shared-command-kit/src/index.ts +410 -0
  135. package/packages/shared-command-kit/src/jobs.ts +132 -0
  136. package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
  137. package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
  138. package/packages/shared-command-kit/src/manifest.ts +127 -0
  139. package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
  140. package/packages/shared-command-kit/src/rest/index.ts +11 -0
  141. package/packages/shared-command-kit/src/studio/index.ts +12 -0
  142. package/packages/shared-command-kit/src/validation/index.ts +6 -0
  143. package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
  144. package/packages/shared-command-kit/src/ws-types.ts +106 -0
  145. package/packages/shared-command-kit/tsconfig.build.json +15 -0
  146. package/packages/shared-command-kit/tsconfig.json +9 -0
  147. package/packages/shared-command-kit/tsup.config.ts +30 -0
  148. package/packages/shared-command-kit/vitest.config.ts +4 -0
  149. package/packages/shared-http/package.json +67 -0
  150. package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
  151. package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
  152. package/packages/shared-http/src/http-observability-collector.ts +363 -0
  153. package/packages/shared-http/src/index.ts +36 -0
  154. package/packages/shared-http/src/log-correlation.ts +89 -0
  155. package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
  156. package/packages/shared-http/src/register-openapi.ts +108 -0
  157. package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
  158. package/packages/shared-http/src/schemas.ts +29 -0
  159. package/packages/shared-http/src/service-observability.ts +63 -0
  160. package/packages/shared-http/tsconfig.build.json +15 -0
  161. package/packages/shared-http/tsconfig.json +9 -0
  162. package/packages/shared-http/tsup.config.ts +23 -0
  163. package/packages/shared-http/vitest.config.ts +13 -0
  164. package/packages/shared-perm-presets/CHANGELOG.md +20 -0
  165. package/packages/shared-perm-presets/README.md +78 -0
  166. package/packages/shared-perm-presets/eslint.config.js +27 -0
  167. package/packages/shared-perm-presets/package.json +45 -0
  168. package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
  169. package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
  170. package/packages/shared-perm-presets/src/combine.ts +278 -0
  171. package/packages/shared-perm-presets/src/index.ts +18 -0
  172. package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
  173. package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
  174. package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
  175. package/packages/shared-perm-presets/src/presets/index.ts +8 -0
  176. package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
  177. package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
  178. package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
  179. package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
  180. package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
  181. package/packages/shared-perm-presets/src/types.ts +192 -0
  182. package/packages/shared-perm-presets/tsconfig.build.json +15 -0
  183. package/packages/shared-perm-presets/tsconfig.json +9 -0
  184. package/packages/shared-perm-presets/tsup.config.ts +8 -0
  185. package/packages/shared-perm-presets/vitest.config.ts +9 -0
  186. package/packages/shared-testing/CHANGELOG.md +20 -0
  187. package/packages/shared-testing/README.md +430 -0
  188. package/packages/shared-testing/package.json +51 -0
  189. package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
  190. package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
  191. package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
  192. package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
  193. package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
  194. package/packages/shared-testing/src/create-test-context.ts +550 -0
  195. package/packages/shared-testing/src/index.ts +77 -0
  196. package/packages/shared-testing/src/mock-cache.ts +179 -0
  197. package/packages/shared-testing/src/mock-llm.ts +319 -0
  198. package/packages/shared-testing/src/mock-logger.ts +97 -0
  199. package/packages/shared-testing/src/mock-storage.ts +108 -0
  200. package/packages/shared-testing/src/setup-platform.ts +101 -0
  201. package/packages/shared-testing/src/test-command.ts +288 -0
  202. package/packages/shared-testing/tsconfig.build.json +15 -0
  203. package/packages/shared-testing/tsconfig.json +9 -0
  204. package/packages/shared-testing/tsup.config.ts +20 -0
  205. package/packages/shared-testing/vitest.config.ts +3 -0
  206. package/packages/shared-tool-kit/CHANGELOG.md +20 -0
  207. package/packages/shared-tool-kit/package.json +47 -0
  208. package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
  209. package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
  210. package/packages/shared-tool-kit/src/factory.ts +126 -0
  211. package/packages/shared-tool-kit/src/index.ts +32 -0
  212. package/packages/shared-tool-kit/src/testing/index.ts +84 -0
  213. package/packages/shared-tool-kit/tsconfig.build.json +15 -0
  214. package/packages/shared-tool-kit/tsconfig.json +9 -0
  215. package/packages/shared-tool-kit/tsup.config.ts +21 -0
  216. package/pnpm-workspace.yaml +11070 -0
  217. package/prettierrc.json +1 -0
  218. package/scripts/devkit-sync.mjs +37 -0
  219. package/scripts/hooks/post-push +9 -0
  220. package/scripts/hooks/pre-commit +9 -0
  221. package/scripts/hooks/pre-push +9 -0
  222. package/tsconfig.base.json +9 -0
  223. package/tsconfig.build.json +15 -0
  224. package/tsconfig.json +9 -0
  225. package/tsconfig.paths.json +50 -0
  226. package/tsconfig.tools.json +18 -0
  227. package/tsup.config.bin.ts +34 -0
  228. package/tsup.config.cli.ts +41 -0
  229. package/tsup.config.dual.ts +46 -0
  230. package/tsup.config.ts +36 -0
  231. package/tsup.external.json +104 -0
  232. 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
+ }