@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,366 @@
1
+ /**
2
+ * Lifecycle Hook Helpers
3
+ *
4
+ * Optional helpers for plugin lifecycle hooks (setup, destroy, upgrade).
5
+ * You can always use plain functions - this is just convenience.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineSetupHandler } from '@kb-labs/shared-command-kit';
10
+ *
11
+ * export const setup = defineSetupHandler({
12
+ * name: 'mind:setup',
13
+ * workspace: {
14
+ * directories: ['.kb/mind/index', '.kb/mind/cache'],
15
+ * },
16
+ * config: {
17
+ * 'kb.config.json': {
18
+ * mind: { enabled: true, scopes: ['default'] },
19
+ * },
20
+ * },
21
+ * async handler(ctx) {
22
+ * ctx.log('Mind workspace initialized');
23
+ * return { ok: true };
24
+ * },
25
+ * });
26
+ * ```
27
+ */
28
+
29
+ import * as fs from 'node:fs/promises';
30
+ import * as path from 'node:path';
31
+ import { existsSync } from 'node:fs';
32
+
33
+ /**
34
+ * Lifecycle handler context
35
+ */
36
+ export interface LifecycleContext {
37
+ /** Output directory (plugin workspace root) */
38
+ outdir: string;
39
+ /** Plugin ID */
40
+ pluginId: string;
41
+ /** Request ID for tracing */
42
+ requestId?: string;
43
+ /** Logger */
44
+ log?: (level: 'debug' | 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
45
+ /** Environment getter */
46
+ env?: (key: string) => string | undefined;
47
+ }
48
+
49
+ /**
50
+ * Workspace setup configuration
51
+ */
52
+ export interface WorkspaceConfig {
53
+ /** Directories to create (relative to outdir or absolute) */
54
+ directories?: string[];
55
+ /** Files to create with content */
56
+ files?: Record<string, string>;
57
+ }
58
+
59
+ /**
60
+ * Config file updates
61
+ */
62
+ export type ConfigUpdates = Record<string, Record<string, unknown>>;
63
+
64
+ /**
65
+ * Setup handler definition
66
+ */
67
+ export interface SetupHandlerDefinition {
68
+ /** Handler name (for logging) */
69
+ name: string;
70
+ /** Workspace configuration (directories/files to create) */
71
+ workspace?: WorkspaceConfig;
72
+ /** Config file updates (will be merged with existing config) */
73
+ config?: ConfigUpdates;
74
+ /** Custom setup logic (optional) */
75
+ handler?: (ctx: LifecycleContext) => Promise<{ ok: boolean; message?: string }>;
76
+ }
77
+
78
+ /**
79
+ * Destroy handler definition
80
+ */
81
+ export interface DestroyHandlerDefinition {
82
+ /** Handler name (for logging) */
83
+ name: string;
84
+ /** Workspace cleanup (directories/files to remove) */
85
+ workspace?: {
86
+ /** Directories to remove */
87
+ directories?: string[];
88
+ /** Files to remove */
89
+ files?: string[];
90
+ };
91
+ /** Config cleanup (keys to remove from config files) */
92
+ config?: Record<string, string[]>; // file -> keys to remove
93
+ /** Custom destroy logic (optional) */
94
+ handler?: (ctx: LifecycleContext) => Promise<{ ok: boolean; message?: string }>;
95
+ }
96
+
97
+ /**
98
+ * Setup handler result
99
+ */
100
+ export interface SetupResult {
101
+ ok: boolean;
102
+ message?: string;
103
+ }
104
+
105
+ /**
106
+ * Define a setup handler with declarative workspace and config setup
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * export const setup = defineSetupHandler({
111
+ * name: 'mind:setup',
112
+ * workspace: {
113
+ * directories: [
114
+ * '.kb/mind/index',
115
+ * '.kb/mind/cache',
116
+ * '.kb/mind/pack',
117
+ * ],
118
+ * files: {
119
+ * '.kb/mind/.gitignore': 'cache/\npack/\n',
120
+ * },
121
+ * },
122
+ * config: {
123
+ * 'kb.config.json': {
124
+ * mind: {
125
+ * enabled: true,
126
+ * scopes: ['default'],
127
+ * },
128
+ * },
129
+ * },
130
+ * async handler(ctx) {
131
+ * ctx.log?.('info', 'Custom setup logic', {});
132
+ * return { ok: true };
133
+ * },
134
+ * });
135
+ * ```
136
+ */
137
+ export function defineSetupHandler(
138
+ definition: SetupHandlerDefinition
139
+ ): (ctx: LifecycleContext) => Promise<SetupResult> {
140
+ return async (ctx: LifecycleContext) => {
141
+ const log = ctx.log || ((level: string, msg: string, meta?: Record<string, unknown>) => {
142
+ console.log(`[${level}] ${msg}`, meta || '');
143
+ });
144
+
145
+ try {
146
+ log('info', 'Setup started', { handler: definition.name, outdir: ctx.outdir });
147
+
148
+ // 1. Create workspace directories and files
149
+ if (definition.workspace) {
150
+ await setupWorkspace(ctx.outdir, definition.workspace, log);
151
+ }
152
+
153
+ // 2. Update config files
154
+ if (definition.config) {
155
+ await updateConfigFiles(ctx.outdir, definition.config, log);
156
+ }
157
+
158
+ // 3. Run custom handler logic
159
+ if (definition.handler) {
160
+ const result = await definition.handler(ctx);
161
+ if (!result.ok) {
162
+ return result;
163
+ }
164
+ }
165
+
166
+ log('info', 'Setup completed', { handler: definition.name });
167
+ return { ok: true, message: `${definition.name} setup completed` };
168
+ } catch (error: any) {
169
+ log('error', 'Setup failed', {
170
+ handler: definition.name,
171
+ error: error.message,
172
+ stack: error.stack,
173
+ });
174
+ return { ok: false, message: `Setup failed: ${error.message}` };
175
+ }
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Define a destroy handler with declarative cleanup
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * export const destroy = defineDestroyHandler({
185
+ * name: 'mind:destroy',
186
+ * workspace: {
187
+ * directories: ['.kb/mind'],
188
+ * },
189
+ * config: {
190
+ * 'kb.config.json': ['mind'], // Remove 'mind' key
191
+ * },
192
+ * });
193
+ * ```
194
+ */
195
+ export function defineDestroyHandler(
196
+ definition: DestroyHandlerDefinition
197
+ ): (ctx: LifecycleContext) => Promise<SetupResult> {
198
+ return async (ctx: LifecycleContext) => {
199
+ const log = ctx.log || ((level: string, msg: string, meta?: Record<string, unknown>) => {
200
+ console.log(`[${level}] ${msg}`, meta || '');
201
+ });
202
+
203
+ try {
204
+ log('info', 'Destroy started', { handler: definition.name, outdir: ctx.outdir });
205
+
206
+ // 1. Clean up config files
207
+ if (definition.config) {
208
+ await cleanupConfigFiles(ctx.outdir, definition.config, log);
209
+ }
210
+
211
+ // 2. Remove workspace directories and files
212
+ if (definition.workspace) {
213
+ await cleanupWorkspace(ctx.outdir, definition.workspace, log);
214
+ }
215
+
216
+ // 3. Run custom handler logic
217
+ if (definition.handler) {
218
+ const result = await definition.handler(ctx);
219
+ if (!result.ok) {
220
+ return result;
221
+ }
222
+ }
223
+
224
+ log('info', 'Destroy completed', { handler: definition.name });
225
+ return { ok: true, message: `${definition.name} destroy completed` };
226
+ } catch (error: any) {
227
+ log('error', 'Destroy failed', {
228
+ handler: definition.name,
229
+ error: error.message,
230
+ stack: error.stack,
231
+ });
232
+ return { ok: false, message: `Destroy failed: ${error.message}` };
233
+ }
234
+ };
235
+ }
236
+
237
+ /**
238
+ * Setup workspace directories and files
239
+ */
240
+ async function setupWorkspace(
241
+ outdir: string,
242
+ config: WorkspaceConfig,
243
+ log: (level: 'error' | 'info' | 'debug' | 'warn', msg: string, meta?: Record<string, unknown>) => void
244
+ ): Promise<void> {
245
+ // Create directories
246
+ if (config.directories) {
247
+ for (const dir of config.directories) {
248
+ const dirPath = path.resolve(outdir, dir);
249
+ await fs.mkdir(dirPath, { recursive: true });
250
+ log('debug', 'Created directory', { path: dirPath });
251
+ }
252
+ }
253
+
254
+ // Create files
255
+ if (config.files) {
256
+ for (const [filePath, content] of Object.entries(config.files)) {
257
+ const fullPath = path.resolve(outdir, filePath);
258
+ const dirPath = path.dirname(fullPath);
259
+ await fs.mkdir(dirPath, { recursive: true });
260
+ await fs.writeFile(fullPath, content, 'utf-8');
261
+ log('debug', 'Created file', { path: fullPath });
262
+ }
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Update config files (merge with existing config)
268
+ */
269
+ async function updateConfigFiles(
270
+ outdir: string,
271
+ updates: ConfigUpdates,
272
+ log: (level: 'error' | 'info' | 'debug' | 'warn', msg: string, meta?: Record<string, unknown>) => void
273
+ ): Promise<void> {
274
+ for (const [configFile, configUpdates] of Object.entries(updates)) {
275
+ const configPath = path.resolve(outdir, configFile);
276
+
277
+ // Read existing config or create empty
278
+ let config: Record<string, unknown> = {};
279
+ if (existsSync(configPath)) {
280
+ const content = await fs.readFile(configPath, 'utf-8');
281
+ try {
282
+ config = JSON.parse(content);
283
+ } catch (error) {
284
+ log('warn', 'Failed to parse existing config, starting fresh', { path: configPath });
285
+ }
286
+ }
287
+
288
+ // Merge updates
289
+ config = { ...config, ...configUpdates };
290
+
291
+ // Write updated config
292
+ const dirPath = path.dirname(configPath);
293
+ await fs.mkdir(dirPath, { recursive: true });
294
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
295
+ log('debug', 'Updated config file', { path: configPath });
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Cleanup workspace directories and files
301
+ */
302
+ async function cleanupWorkspace(
303
+ outdir: string,
304
+ config: DestroyHandlerDefinition['workspace'],
305
+ log: (level: 'error' | 'info' | 'debug' | 'warn', msg: string, meta?: Record<string, unknown>) => void
306
+ ): Promise<void> {
307
+ if (!config) {return;}
308
+
309
+ // Remove files first
310
+ if (config.files) {
311
+ for (const file of config.files) {
312
+ const filePath = path.resolve(outdir, file);
313
+ if (existsSync(filePath)) {
314
+ await fs.unlink(filePath);
315
+ log('debug', 'Removed file', { path: filePath });
316
+ }
317
+ }
318
+ }
319
+
320
+ // Remove directories
321
+ if (config.directories) {
322
+ for (const dir of config.directories) {
323
+ const dirPath = path.resolve(outdir, dir);
324
+ if (existsSync(dirPath)) {
325
+ await fs.rm(dirPath, { recursive: true, force: true });
326
+ log('debug', 'Removed directory', { path: dirPath });
327
+ }
328
+ }
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Cleanup config files (remove keys)
334
+ */
335
+ async function cleanupConfigFiles(
336
+ outdir: string,
337
+ cleanups: Record<string, string[]>,
338
+ log: (level: 'error' | 'info' | 'debug' | 'warn', msg: string, meta?: Record<string, unknown>) => void
339
+ ): Promise<void> {
340
+ for (const [configFile, keysToRemove] of Object.entries(cleanups)) {
341
+ const configPath = path.resolve(outdir, configFile);
342
+
343
+ if (!existsSync(configPath)) {
344
+ continue;
345
+ }
346
+
347
+ // Read config
348
+ const content = await fs.readFile(configPath, 'utf-8');
349
+ let config: Record<string, unknown>;
350
+ try {
351
+ config = JSON.parse(content);
352
+ } catch (error) {
353
+ log('warn', 'Failed to parse config for cleanup', { path: configPath });
354
+ continue;
355
+ }
356
+
357
+ // Remove keys
358
+ for (const key of keysToRemove) {
359
+ delete config[key];
360
+ }
361
+
362
+ // Write updated config
363
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
364
+ log('debug', 'Cleaned up config file', { path: configPath, removed: keysToRemove });
365
+ }
366
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module @kb-labs/shared-command-kit/lifecycle
3
+ * Lifecycle hook helpers (setup, destroy, upgrade)
4
+ */
5
+
6
+ export * from './define-handlers';
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Manifest definition helpers
3
+ * @module @kb-labs/shared-command-kit/manifest
4
+ */
5
+
6
+ import type { ManifestV3 } from '@kb-labs/plugin-contracts';
7
+
8
+ /**
9
+ * Define a ManifestV3 with type safety
10
+ *
11
+ * This is a type-safe wrapper for creating ManifestV3 objects.
12
+ * It provides compile-time validation via TypeScript and optional
13
+ * runtime validation via Zod (if enabled).
14
+ *
15
+ * For built plugins (dist/), this function is compiled away and only
16
+ * the plain object remains, avoiding runtime dependencies.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Basic usage
21
+ * export const manifest = defineManifest({
22
+ * schema: 'kb.plugin/2',
23
+ * id: '@kb-labs/my-plugin',
24
+ * version: '1.0.0',
25
+ * cli: {
26
+ * commands: [...]
27
+ * }
28
+ * });
29
+ *
30
+ * // With contracts typing (Level 2)
31
+ * import type { PluginContracts } from '@kb-labs/my-plugin-contracts';
32
+ *
33
+ * export const manifest = defineManifest<typeof pluginContractsManifest>({
34
+ * schema: 'kb.plugin/2',
35
+ * id: '@kb-labs/my-plugin',
36
+ * artifacts: [
37
+ * { id: 'my.artifact.id' } // ✅ Type-checked against contracts
38
+ * ],
39
+ * cli: {
40
+ * commands: [{
41
+ * id: 'my:command', // ✅ Type-checked against contracts
42
+ * // ...
43
+ * }]
44
+ * }
45
+ * });
46
+ * ```
47
+ *
48
+ * @param manifest - Manifest configuration
49
+ * @returns ManifestV3 object
50
+ */
51
+ export function defineManifest<TContracts = unknown>(
52
+ manifest: ManifestV3
53
+ ): ManifestV3 {
54
+ // In development, you could add runtime validation here if needed:
55
+ // if (process.env.NODE_ENV === 'development') {
56
+ // validateManifestV3(manifest);
57
+ // }
58
+
59
+ // For production builds, this is just an identity function
60
+ // that provides type safety at compile time
61
+ return manifest;
62
+ }
63
+
64
+ /**
65
+ * Flag schema definition (compatible with defineFlags)
66
+ */
67
+ type FlagSchemaDefinition = Record<
68
+ string,
69
+ {
70
+ type: 'string' | 'boolean' | 'number' | 'array';
71
+ alias?: string;
72
+ default?: unknown;
73
+ description?: string;
74
+ choices?: string[];
75
+ required?: boolean;
76
+ }
77
+ >;
78
+
79
+ /**
80
+ * Convert flag schema definition to CliFlagDecl[] for manifest
81
+ *
82
+ * This helper converts the flag schema format used in defineCommand
83
+ * to the array format expected in ManifestV3.
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const helloFlags = {
88
+ * name: { type: 'string', description: 'Name to greet', alias: 'n' },
89
+ * json: { type: 'boolean', description: 'Emit JSON', default: false }
90
+ * };
91
+ *
92
+ * const manifestFlags = defineCommandFlags(helloFlags);
93
+ * // Use in manifest: flags: manifestFlags
94
+ *
95
+ * // In manifest.v2.ts:
96
+ * export const manifest = defineManifest({
97
+ * cli: {
98
+ * commands: [{
99
+ * id: 'hello',
100
+ * flags: defineCommandFlags(helloFlags),
101
+ * // ...
102
+ * }]
103
+ * }
104
+ * });
105
+ * ```
106
+ */
107
+ export function defineCommandFlags<TFlags extends FlagSchemaDefinition>(
108
+ flags: TFlags
109
+ ): Array<{
110
+ name: string;
111
+ type: 'string' | 'boolean' | 'number' | 'array';
112
+ alias?: string;
113
+ default?: unknown;
114
+ description?: string;
115
+ choices?: string[];
116
+ required?: boolean;
117
+ }> {
118
+ return Object.entries(flags).map(([name, flag]) => ({
119
+ name,
120
+ type: flag.type,
121
+ ...(flag.alias !== undefined && { alias: flag.alias }),
122
+ ...(flag.default !== undefined && { default: flag.default }),
123
+ ...(flag.description !== undefined && { description: flag.description }),
124
+ ...(flag.choices !== undefined && { choices: flag.choices }),
125
+ ...(flag.required !== undefined && { required: flag.required }),
126
+ }));
127
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * REST Handler Definition (V3)
3
+ *
4
+ * Define REST handlers compatible with plugin-runtime's runInProcess().
5
+ * Returns { execute } object with (ctx, input) signature.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { defineHandler } from '@kb-labs/shared-command-kit';
10
+ *
11
+ * export default defineHandler({
12
+ * async execute(ctx, input: { scope?: string }) {
13
+ * const plan = await loadPlan(ctx.cwd);
14
+ * return plan;
15
+ * }
16
+ * });
17
+ * ```
18
+ */
19
+
20
+ import type { PluginContextV3 } from '@kb-labs/plugin-contracts';
21
+
22
+ /**
23
+ * REST input structure from route-mounter.
24
+ * Query params, body, and route params are separated to avoid conflicts.
25
+ */
26
+ export interface RestInput<TQuery = unknown, TBody = unknown, TParams = unknown> {
27
+ query?: TQuery;
28
+ body?: TBody;
29
+ params?: TParams;
30
+ }
31
+
32
+ /**
33
+ * Handler interface expected by runInProcess().
34
+ */
35
+ export interface Handler<TConfig = unknown, TInput = unknown, TOutput = unknown> {
36
+ /**
37
+ * Execute the handler
38
+ */
39
+ execute(
40
+ ctx: PluginContextV3<TConfig>,
41
+ input: TInput
42
+ ): Promise<TOutput>;
43
+ }
44
+
45
+ /**
46
+ * Handler definition options.
47
+ * Extensible for future features (inputSchema, middleware, etc.)
48
+ */
49
+ export interface HandlerDefinition<TConfig = unknown, TInput = unknown, TOutput = unknown> {
50
+ /**
51
+ * Execute the handler.
52
+ * Returns data directly - no need for exitCode/CommandResult wrapper.
53
+ *
54
+ * @param ctx - Plugin context with runtime APIs (fs, fetch, env, ui, etc.)
55
+ * @param input - Request input (body for POST, query for GET, etc.)
56
+ * @returns Response data (will be serialized as JSON)
57
+ * @throws Error on failure (will be converted to HTTP 500)
58
+ */
59
+ execute(
60
+ ctx: PluginContextV3<TConfig>,
61
+ input: TInput
62
+ ): Promise<TOutput>;
63
+
64
+ // Future options (non-breaking additions):
65
+ // inputSchema?: ZodType<TInput>;
66
+ // outputSchema?: ZodType<TOutput>;
67
+ // middleware?: Middleware[];
68
+ // timeout?: number;
69
+ }
70
+
71
+ /**
72
+ * Define a REST handler
73
+ *
74
+ * Creates a handler object compatible with runInProcess() from plugin-runtime.
75
+ * The handler receives full PluginContextV3 with runtime APIs.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * // Simple handler
80
+ * export default defineHandler({
81
+ * async execute(ctx, input: { scope?: string }) {
82
+ * const plan = await loadPlan(ctx.cwd);
83
+ * return plan;
84
+ * }
85
+ * });
86
+ *
87
+ * // With typed query parameters using RestInput
88
+ * import { defineHandler, type RestInput } from '@kb-labs/sdk';
89
+ *
90
+ * export default defineHandler({
91
+ * async execute(ctx, input: RestInput<{ workspace?: string }>) {
92
+ * const workspace = input.query?.workspace || 'root';
93
+ * return { workspace };
94
+ * }
95
+ * });
96
+ *
97
+ * // With typed query and body
98
+ * interface QueryParams {
99
+ * workspace?: string;
100
+ * }
101
+ *
102
+ * interface BodyParams {
103
+ * name: string;
104
+ * description?: string;
105
+ * }
106
+ *
107
+ * export default defineHandler({
108
+ * async execute(ctx, input: RestInput<QueryParams, BodyParams>) {
109
+ * const workspace = input.query?.workspace || 'root';
110
+ * const name = input.body?.name;
111
+ * return { workspace, name };
112
+ * }
113
+ * });
114
+ *
115
+ * // With typed generics
116
+ * interface GetPlanInput {
117
+ * scope?: string;
118
+ * includeHistory?: boolean;
119
+ * }
120
+ *
121
+ * interface ReleasePlan {
122
+ * version: string;
123
+ * packages: string[];
124
+ * }
125
+ *
126
+ * export default defineHandler<unknown, GetPlanInput, ReleasePlan>({
127
+ * async execute(ctx, input) {
128
+ * // ctx: PluginContextV3 with runtime.fs, ui, etc.
129
+ * // input: GetPlanInput (typed)
130
+ * // return: ReleasePlan (typed)
131
+ * return await loadPlan(ctx.cwd, input.scope);
132
+ * }
133
+ * });
134
+ *
135
+ * // Using runtime APIs
136
+ * export default defineHandler({
137
+ * async execute(ctx, input: { path: string }) {
138
+ * // File system access
139
+ * const content = await ctx.runtime.fs.readFile(input.path, 'utf-8');
140
+ *
141
+ * // Environment variables
142
+ * const apiKey = ctx.runtime.env('API_KEY');
143
+ *
144
+ * // Logging
145
+ * ctx.runtime.log('info', 'Processing file', { path: input.path });
146
+ *
147
+ * return { content, hasApiKey: !!apiKey };
148
+ * }
149
+ * });
150
+ * ```
151
+ */
152
+ export function defineHandler<TConfig = unknown, TInput = unknown, TOutput = unknown>(
153
+ definition: HandlerDefinition<TConfig, TInput, TOutput>
154
+ ): Handler<TConfig, TInput, TOutput> {
155
+ // Return handler object compatible with runInProcess()
156
+ // Currently passes through, but this wrapper enables:
157
+ // - Future input validation via inputSchema
158
+ // - Future output validation via outputSchema
159
+ // - Middleware execution
160
+ // - Logging/tracing hooks
161
+ // - Error normalization
162
+ return {
163
+ execute: async (ctx, input) => {
164
+ // Future: validate input against schema
165
+ // if (definition.inputSchema) {
166
+ // input = definition.inputSchema.parse(input);
167
+ // }
168
+
169
+ // Future: run pre-middleware
170
+ // for (const mw of definition.middleware ?? []) {
171
+ // await mw.before?.(ctx, input);
172
+ // }
173
+
174
+ // Future: validate output against schema
175
+ // if (definition.outputSchema) {
176
+ // definition.outputSchema.parse(result);
177
+ // }
178
+
179
+ // Future: run post-middleware
180
+ // for (const mw of definition.middleware ?? []) {
181
+ // await mw.after?.(ctx, input, result);
182
+ // }
183
+
184
+ return definition.execute(ctx, input);
185
+ },
186
+ };
187
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @module @kb-labs/shared-command-kit/rest
3
+ * REST handler definition helpers
4
+ */
5
+
6
+ export {
7
+ defineHandler,
8
+ type Handler,
9
+ type HandlerDefinition,
10
+ type RestInput,
11
+ } from './define-handler.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module @kb-labs/shared-command-kit/studio
3
+ *
4
+ * Studio V2 types re-exported for convenience.
5
+ * Plugin authors use `@kb-labs/sdk/studio` instead.
6
+ */
7
+
8
+ export type {
9
+ StudioConfig,
10
+ StudioPageEntry,
11
+ StudioMenuEntry,
12
+ } from '@kb-labs/plugin-contracts';