@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,151 @@
1
+ /**
2
+ * CLI manifest parsing utilities
3
+ */
4
+
5
+ export interface CommandManifest {
6
+ manifestVersion: string;
7
+ id: string;
8
+ aliases?: string[];
9
+ group: string;
10
+ describe: string;
11
+ longDescription?: string;
12
+ requires?: string[];
13
+ flags?: FlagDefinition[];
14
+ examples?: string[];
15
+ loader: () => Promise<{ run: any }>;
16
+ }
17
+
18
+ export interface FlagDefinition {
19
+ name: string;
20
+ type: 'string' | 'boolean' | 'number' | 'array';
21
+ alias?: string;
22
+ default?: any;
23
+ description?: string;
24
+ choices?: string[];
25
+ required?: boolean;
26
+ }
27
+
28
+ /**
29
+ * Extract command IDs from a manifest
30
+ */
31
+ export function extractCommandIds(manifest: CommandManifest[]): string[] {
32
+ return manifest.map(cmd => cmd.id);
33
+ }
34
+
35
+ /**
36
+ * Extract command groups from a manifest
37
+ */
38
+ export function extractCommandGroups(manifest: CommandManifest[]): string[] {
39
+ const groups = new Set<string>();
40
+ manifest.forEach(cmd => groups.add(cmd.group));
41
+ return Array.from(groups);
42
+ }
43
+
44
+ /**
45
+ * Find commands by group
46
+ */
47
+ export function findCommandsByGroup(manifest: CommandManifest[], group: string): CommandManifest[] {
48
+ return manifest.filter(cmd => cmd.group === group);
49
+ }
50
+
51
+ /**
52
+ * Find command by ID
53
+ */
54
+ export function findCommandById(manifest: CommandManifest[], id: string): CommandManifest | undefined {
55
+ return manifest.find(cmd => cmd.id === id);
56
+ }
57
+
58
+ /**
59
+ * Get command info for suggestions
60
+ */
61
+ export function getCommandInfo(manifest: CommandManifest[], commandId: string): {
62
+ id: string;
63
+ group: string;
64
+ name: string;
65
+ description: string;
66
+ available: boolean;
67
+ } | null {
68
+ const cmd = findCommandById(manifest, commandId);
69
+ if (!cmd) {
70
+ return null;
71
+ }
72
+
73
+ const [group, name] = commandId.split(':');
74
+ return {
75
+ id: commandId,
76
+ group: group || 'unknown',
77
+ name: name || commandId,
78
+ description: cmd.describe,
79
+ available: true
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Generate suggestions for a specific group
85
+ */
86
+ export function generateGroupSuggestions(
87
+ manifest: CommandManifest[],
88
+ group: string,
89
+ _warningCodes: Set<string>,
90
+ _context: any
91
+ ): Array<{
92
+ id: string;
93
+ command: string;
94
+ args: string[];
95
+ description: string;
96
+ impact: 'safe' | 'disruptive';
97
+ when: string;
98
+ available: boolean;
99
+ }> {
100
+ const groupCommands = findCommandsByGroup(manifest, group);
101
+ const suggestions: Array<{
102
+ id: string;
103
+ command: string;
104
+ args: string[];
105
+ description: string;
106
+ impact: 'safe' | 'disruptive';
107
+ when: string;
108
+ available: boolean;
109
+ }> = [];
110
+
111
+ // Generate common suggestions for each group
112
+ for (const cmd of groupCommands) {
113
+ // Basic command suggestion
114
+ suggestions.push({
115
+ id: `${cmd.group.toUpperCase()}_${cmd.id.split(':')[1]?.toUpperCase() || 'UNKNOWN'}`,
116
+ command: `kb ${cmd.group} ${cmd.id.split(':')[1] || ''}`,
117
+ args: [],
118
+ description: cmd.describe,
119
+ impact: 'safe',
120
+ when: 'GENERAL',
121
+ available: true
122
+ });
123
+
124
+ // Add specific suggestions based on command type
125
+ if (cmd.id.includes('init')) {
126
+ suggestions.push({
127
+ id: `${cmd.group.toUpperCase()}_INIT_FORCE`,
128
+ command: `kb ${cmd.group} ${cmd.id.split(':')[1] || ''}`,
129
+ args: ['--force'],
130
+ description: `${cmd.describe} (force)`,
131
+ impact: 'disruptive',
132
+ when: 'INIT_FAILED',
133
+ available: true
134
+ });
135
+ }
136
+
137
+ if (cmd.id.includes('clean') || cmd.id.includes('reset')) {
138
+ suggestions.push({
139
+ id: `${cmd.group.toUpperCase()}_CLEAN_HARD`,
140
+ command: `kb ${cmd.group} ${cmd.id.split(':')[1] || ''}`,
141
+ args: ['--hard'],
142
+ description: `${cmd.describe} (hard)`,
143
+ impact: 'disruptive',
144
+ when: 'CLEAN_NEEDED',
145
+ available: true
146
+ });
147
+ }
148
+ }
149
+
150
+ return suggestions;
151
+ }
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Modern CLI formatting utilities with side border design
3
+ * Provides minimalist, modern UI components for CLI output
4
+ */
5
+
6
+ import { safeColors, safeSymbols } from './colors';
7
+ import { stripAnsi, bulletList as baseBulletList } from './format';
8
+ import { formatTiming as baseFormatTiming } from './command-output';
9
+
10
+ /**
11
+ * Side border box - modern minimalist design
12
+ *
13
+ * @example
14
+ * ```
15
+ * ┌── Command Name
16
+ * │
17
+ * │ Section Header
18
+ * │ Key: value
19
+ * │
20
+ * └── ✓ Success / 12ms
21
+ * ```
22
+ */
23
+ export interface SideBorderBoxOptions {
24
+ title: string;
25
+ sections: SectionContent[];
26
+ footer?: string;
27
+ status?: 'success' | 'error' | 'warning' | 'info';
28
+ timing?: number;
29
+ }
30
+
31
+ export interface SectionContent {
32
+ header?: string;
33
+ items: string[];
34
+ }
35
+
36
+ /**
37
+ * Create a side-bordered box with modern design
38
+ */
39
+ export function sideBorderBox(options: SideBorderBoxOptions): string {
40
+ const { title, sections, footer, status, timing } = options;
41
+ const lines: string[] = [];
42
+
43
+ // Top border with title (using top-left corner)
44
+ const titleLine = `${safeSymbols.topLeft}${safeSymbols.separator.repeat(2)} ${safeColors.primary(safeColors.bold(title))}`;
45
+ lines.push(titleLine);
46
+ lines.push(safeSymbols.border);
47
+
48
+ // Sections
49
+ for (let i = 0; i < sections.length; i++) {
50
+ const section = sections[i];
51
+ if (!section) {continue;}
52
+
53
+ // Section header (optional)
54
+ if (section.header) {
55
+ lines.push(`${safeSymbols.border} ${safeColors.bold(section.header)}`);
56
+ }
57
+
58
+ // Section items
59
+ for (const item of section.items) {
60
+ lines.push(`${safeSymbols.border} ${item}`);
61
+ }
62
+
63
+ // Add spacing between sections (but not after the last one)
64
+ if (i < sections.length - 1) {
65
+ lines.push(safeSymbols.border);
66
+ }
67
+ }
68
+
69
+ // Bottom border with status/timing
70
+ if (footer || status || timing !== undefined) {
71
+ lines.push(safeSymbols.border);
72
+ const footerParts: string[] = [];
73
+
74
+ if (footer) {
75
+ footerParts.push(footer);
76
+ } else if (status) {
77
+ const statusSymbol = getStatusSymbol(status);
78
+ const statusText = getStatusText(status);
79
+ const statusColor = getStatusColor(status);
80
+ footerParts.push(statusColor(`${statusSymbol} ${statusText}`));
81
+ }
82
+
83
+ if (timing !== undefined) {
84
+ footerParts.push(formatTiming(timing));
85
+ }
86
+
87
+ const footerLine = `${safeSymbols.bottomLeft}${safeSymbols.separator.repeat(2)} ${footerParts.join(' / ')}`;
88
+ lines.push(footerLine);
89
+ }
90
+
91
+ return lines.join('\n');
92
+ }
93
+
94
+ /**
95
+ * Format a section header
96
+ */
97
+ export function sectionHeader(text: string): string {
98
+ return safeColors.bold(text);
99
+ }
100
+
101
+ /**
102
+ * Format metrics list (key: value pairs with aligned values)
103
+ */
104
+ export function metricsList(metrics: Record<string, string | number>): string[] {
105
+ const entries = Object.entries(metrics);
106
+ if (entries.length === 0) {return [];}
107
+
108
+ // Find max key length for alignment
109
+ const maxKeyLength = Math.max(
110
+ ...entries.map(([key]) => stripAnsi(key).length)
111
+ );
112
+
113
+ return entries.map(([key, value]) => {
114
+ const keyLength = stripAnsi(key).length;
115
+ const padding = ' '.repeat(maxKeyLength - keyLength + 2);
116
+ const formattedKey = safeColors.bold(key);
117
+ const formattedValue = safeColors.muted(String(value));
118
+ return `${formattedKey}:${padding}${formattedValue}`;
119
+ });
120
+ }
121
+
122
+ /**
123
+ * Format a bullet list (re-exported from base utilities)
124
+ */
125
+ export const bulletList = baseBulletList;
126
+
127
+ /**
128
+ * Format timing (re-exported from command-output)
129
+ */
130
+ export const formatTiming = baseFormatTiming;
131
+
132
+ /**
133
+ * Format status line for footer
134
+ */
135
+ export function statusLine(
136
+ status: 'success' | 'error' | 'warning' | 'info',
137
+ timing?: number
138
+ ): string {
139
+ const symbol = getStatusSymbol(status);
140
+ const text = getStatusText(status);
141
+ const color = getStatusColor(status);
142
+
143
+ const parts = [color(`${symbol} ${text}`)];
144
+
145
+ if (timing !== undefined) {
146
+ parts.push(formatTiming(timing));
147
+ }
148
+
149
+ return parts.join(' / ');
150
+ }
151
+
152
+ // Helper functions
153
+
154
+ function getStatusSymbol(status: 'success' | 'error' | 'warning' | 'info'): string {
155
+ switch (status) {
156
+ case 'success':
157
+ return safeSymbols.success;
158
+ case 'error':
159
+ return safeSymbols.error;
160
+ case 'warning':
161
+ return safeSymbols.warning;
162
+ case 'info':
163
+ return safeSymbols.info;
164
+ }
165
+ }
166
+
167
+ function getStatusText(status: 'success' | 'error' | 'warning' | 'info'): string {
168
+ switch (status) {
169
+ case 'success':
170
+ return 'Success';
171
+ case 'error':
172
+ return 'Failed';
173
+ case 'warning':
174
+ return 'Warning';
175
+ case 'info':
176
+ return 'Info';
177
+ }
178
+ }
179
+
180
+ function getStatusColor(status: 'success' | 'error' | 'warning' | 'info'): (text: string) => string {
181
+ switch (status) {
182
+ case 'success':
183
+ return safeColors.success;
184
+ case 'error':
185
+ return safeColors.error;
186
+ case 'warning':
187
+ return safeColors.warning;
188
+ case 'info':
189
+ return safeColors.info;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Format command help in modern side-border style
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * const help = formatCommandHelp({
199
+ * title: 'kb version',
200
+ * description: 'Show CLI version',
201
+ * longDescription: 'Displays the current version...',
202
+ * examples: ['kb version', 'kb version --json'],
203
+ * flags: [{name: 'json', description: 'Output in JSON'}]
204
+ * });
205
+ * ```
206
+ */
207
+ export function formatCommandHelp(options: {
208
+ title: string;
209
+ description?: string;
210
+ longDescription?: string;
211
+ examples?: string[];
212
+ flags?: Array<{ name: string; alias?: string; description?: string; required?: boolean }>;
213
+ aliases?: string[];
214
+ }): string {
215
+ const { title, description, longDescription, examples, flags, aliases } = options;
216
+ const sections: SectionContent[] = [];
217
+
218
+ // Description section
219
+ if (description) {
220
+ sections.push({
221
+ header: 'Description',
222
+ items: [description],
223
+ });
224
+ }
225
+
226
+ // Long description
227
+ if (longDescription) {
228
+ sections.push({
229
+ header: 'Details',
230
+ items: [longDescription],
231
+ });
232
+ }
233
+
234
+ // Aliases
235
+ if (aliases && aliases.length > 0) {
236
+ sections.push({
237
+ header: 'Aliases',
238
+ items: aliases.map(a => safeColors.muted(a)),
239
+ });
240
+ }
241
+
242
+ // Flags
243
+ if (flags && flags.length > 0) {
244
+ const flagItems = flags.map(flag => {
245
+ const label = flag.alias
246
+ ? `--${flag.name}, -${flag.alias}`
247
+ : `--${flag.name}`;
248
+ const required = flag.required ? safeColors.warning(' (required)') : '';
249
+ const desc = flag.description ? safeColors.muted(` — ${flag.description}`) : '';
250
+ return `${safeColors.bold(label)}${required}${desc}`;
251
+ });
252
+ sections.push({
253
+ header: 'Flags',
254
+ items: flagItems,
255
+ });
256
+ }
257
+
258
+ // Examples
259
+ if (examples && examples.length > 0) {
260
+ sections.push({
261
+ header: 'Examples',
262
+ items: examples.map(ex => safeColors.muted(` ${ex}`)),
263
+ });
264
+ }
265
+
266
+ return sideBorderBox({
267
+ title,
268
+ sections,
269
+ status: 'info',
270
+ });
271
+ }
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Multi-CLI suggestions system
3
+ * Supports multiple CLI packages with their own manifests
4
+ */
5
+
6
+ import {
7
+ createCommandRegistry,
8
+ generateDevlinkSuggestions,
9
+ type CommandSuggestion,
10
+ type CommandRegistry
11
+ } from './command-suggestions';
12
+ import {
13
+ extractCommandIds,
14
+ generateGroupSuggestions,
15
+ type CommandManifest
16
+ } from './manifest-parser';
17
+
18
+ export interface MultiCLIContext {
19
+ warningCodes: Set<string>;
20
+ [key: string]: any;
21
+ }
22
+
23
+ export interface CLIPackage {
24
+ name: string;
25
+ group: string;
26
+ commands: CommandManifest[];
27
+ priority: number; // Higher priority = more important suggestions
28
+ }
29
+
30
+ /**
31
+ * Multi-CLI suggestions manager
32
+ */
33
+ export class MultiCLISuggestions {
34
+ private packages: Map<string, CLIPackage> = new Map();
35
+ private globalRegistry: CommandRegistry | null = null;
36
+
37
+ /**
38
+ * Register a CLI package
39
+ */
40
+ registerPackage(pkg: CLIPackage): void {
41
+ this.packages.set(pkg.name, pkg);
42
+ this.globalRegistry = null; // Invalidate cache
43
+ }
44
+
45
+ /**
46
+ * Get or create global command registry
47
+ */
48
+ private getGlobalRegistry(): CommandRegistry {
49
+ if (this.globalRegistry) {
50
+ return this.globalRegistry;
51
+ }
52
+
53
+ const allCommands: string[] = [];
54
+ for (const pkg of this.packages.values()) {
55
+ allCommands.push(...extractCommandIds(pkg.commands));
56
+ }
57
+
58
+ this.globalRegistry = createCommandRegistry(allCommands);
59
+ return this.globalRegistry;
60
+ }
61
+
62
+ /**
63
+ * Generate suggestions for a specific group
64
+ */
65
+ generateGroupSuggestions(
66
+ group: string,
67
+ context: MultiCLIContext
68
+ ): CommandSuggestion[] {
69
+ const suggestions: CommandSuggestion[] = [];
70
+ const registry = this.getGlobalRegistry();
71
+
72
+ // Find packages for this group
73
+ const groupPackages = Array.from(this.packages.values())
74
+ .filter(pkg => pkg.group === group)
75
+ .sort((a, b) => b.priority - a.priority);
76
+
77
+ for (const pkg of groupPackages) {
78
+ const groupSuggestions = generateGroupSuggestions(
79
+ pkg.commands,
80
+ group,
81
+ context.warningCodes,
82
+ context
83
+ );
84
+
85
+ // Convert to CommandSuggestion format and validate
86
+ for (const suggestion of groupSuggestions) {
87
+ if (registry.commands.has(suggestion.command.replace('kb ', '').replace(' ', ':'))) {
88
+ suggestions.push({
89
+ id: suggestion.id,
90
+ command: suggestion.command,
91
+ args: suggestion.args,
92
+ description: suggestion.description,
93
+ impact: suggestion.impact,
94
+ when: suggestion.when,
95
+ available: true
96
+ });
97
+ }
98
+ }
99
+ }
100
+
101
+ return suggestions;
102
+ }
103
+
104
+ /**
105
+ * Generate all suggestions across all packages
106
+ */
107
+ generateAllSuggestions(context: MultiCLIContext): CommandSuggestion[] {
108
+ const suggestions: CommandSuggestion[] = [];
109
+ const registry = this.getGlobalRegistry();
110
+
111
+ // Special handling for devlink (highest priority)
112
+ if (this.packages.has('devlink')) {
113
+ const devlinkSuggestions = generateDevlinkSuggestions(
114
+ context.warningCodes,
115
+ { undo: context.undo },
116
+ registry
117
+ );
118
+ suggestions.push(...devlinkSuggestions);
119
+ }
120
+
121
+ // Generate suggestions for other groups
122
+ const groups = new Set<string>();
123
+ for (const pkg of this.packages.values()) {
124
+ groups.add(pkg.group);
125
+ }
126
+
127
+ for (const group of groups) {
128
+ if (group !== 'devlink') { // Skip devlink as it's handled above
129
+ const groupSuggestions = this.generateGroupSuggestions(group, context);
130
+ suggestions.push(...groupSuggestions);
131
+ }
132
+ }
133
+
134
+ return suggestions;
135
+ }
136
+
137
+ /**
138
+ * Get available commands for a group
139
+ */
140
+ getAvailableCommands(group: string): string[] {
141
+ const groupPackages = Array.from(this.packages.values())
142
+ .filter(pkg => pkg.group === group);
143
+
144
+ const commands: string[] = [];
145
+ for (const pkg of groupPackages) {
146
+ commands.push(...extractCommandIds(pkg.commands));
147
+ }
148
+
149
+ return commands;
150
+ }
151
+
152
+ /**
153
+ * Get all registered packages
154
+ */
155
+ getPackages(): CLIPackage[] {
156
+ return Array.from(this.packages.values());
157
+ }
158
+ }
159
+
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Table formatting utilities for CLI output
3
+ * Handles proper column alignment accounting for emoji/unicode width
4
+ */
5
+
6
+ /**
7
+ * Calculate visual width of a string (accounting for emoji/wide chars and ANSI codes)
8
+ * Simplified version - emoji count as 2 chars, ANSI codes are stripped
9
+ */
10
+ function visualWidth(str: string): number {
11
+ // Remove ANSI escape codes first
12
+ const ansiRegex = /\x1B\[[0-9;]*[a-zA-Z]/g;
13
+ const cleanStr = str.replace(ansiRegex, '');
14
+
15
+ // Count emoji and wide characters as 2
16
+ const emojiRegex = /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]|[\u{2190}-\u{21FF}]|[\u{2300}-\u{23FF}]|[\u{24C2}-\u{1F251}]|[\u{2B50}-\u{2B55}]|[\u{3030}-\u{303D}]|[\u{3297}-\u{3299}]|[\u{1F000}-\u{1F02F}]|[\u{1F0A0}-\u{1F0FF}]|[\u{1F100}-\u{1F1FF}]|[\u{1F200}-\u{1F2FF}]|[\u{1F300}-\u{1F5FF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F700}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]|[\u{23F0}-\u{23FF}]|[\u{25A0}-\u{25FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{FE00}-\u{FE0F}]|[\u{1F1E6}-\u{1F1FF}]|[\u{1F3FB}-\u{1F3FF}]|[\u{1F9B0}-\u{1F9B3}]|[\u{20E3}]|[\u{FE0F}]/gu;
17
+ const emojiMatches = cleanStr.match(emojiRegex);
18
+ const emojiCount = emojiMatches ? emojiMatches.length : 0;
19
+
20
+ // Basic character count minus emoji count (since emoji are already counted)
21
+ // Then add emoji count * 2 for visual width
22
+ return cleanStr.length - emojiCount + emojiCount * 2;
23
+ }
24
+
25
+ /**
26
+ * Pad string to visual width (accounting for emoji)
27
+ */
28
+ function padVisual(str: string, width: number, padChar: string = ' '): string {
29
+ const vWidth = visualWidth(str);
30
+ const padding = Math.max(0, width - vWidth);
31
+ return str + padChar.repeat(padding);
32
+ }
33
+
34
+ export interface TableColumn {
35
+ header: string;
36
+ width?: number; // Auto-calculated if not provided
37
+ align?: 'left' | 'right' | 'center';
38
+ }
39
+
40
+ export interface TableOptions {
41
+ header?: boolean;
42
+ separator?: string; // Character for separator line (e.g., '─')
43
+ padding?: number; // Padding between columns
44
+ }
45
+
46
+ /**
47
+ * Format data as a table with proper column alignment
48
+ */
49
+ export function formatTable(
50
+ columns: TableColumn[],
51
+ rows: string[][],
52
+ options: TableOptions = {}
53
+ ): string[] {
54
+ const { header = true, separator = '─', padding = 1 } = options;
55
+
56
+ // Calculate column widths
57
+ const widths: number[] = columns.map((col, idx) => {
58
+ if (col.width !== undefined) {
59
+ return col.width;
60
+ }
61
+
62
+ // Calculate max width from header and all rows
63
+ let maxWidth = visualWidth(col.header);
64
+ for (const row of rows) {
65
+ if (row[idx]) {
66
+ maxWidth = Math.max(maxWidth, visualWidth(String(row[idx])));
67
+ }
68
+ }
69
+ return maxWidth;
70
+ });
71
+
72
+ const lines: string[] = [];
73
+
74
+ // Header
75
+ if (header) {
76
+ const headerCells = columns.map((col, idx) => {
77
+ const cell = col.header;
78
+ const width = widths[idx]!;
79
+ return padVisual(cell, width);
80
+ });
81
+ lines.push(headerCells.join(' '.repeat(padding)));
82
+
83
+ // Separator
84
+ if (separator) {
85
+ const separatorLine = widths.map(w => separator.repeat(w)).join(' '.repeat(padding));
86
+ lines.push(separatorLine);
87
+ }
88
+ }
89
+
90
+ // Rows
91
+ for (const row of rows) {
92
+ const cells = columns.map((col, idx) => {
93
+ const cell = row[idx] !== undefined ? String(row[idx]) : '';
94
+ const align = col.align || 'left';
95
+ const width = widths[idx]!;
96
+
97
+ if (align === 'right') {
98
+ const vWidth = visualWidth(cell);
99
+ const padding = Math.max(0, width - vWidth);
100
+ return ' '.repeat(padding) + cell;
101
+ } else if (align === 'center') {
102
+ const vWidth = visualWidth(cell);
103
+ const padding = Math.max(0, width - vWidth);
104
+ const leftPad = Math.floor(padding / 2);
105
+ const rightPad = padding - leftPad;
106
+ return ' '.repeat(leftPad) + cell + ' '.repeat(rightPad);
107
+ } else {
108
+ return padVisual(cell, width);
109
+ }
110
+ });
111
+ lines.push(cells.join(' '.repeat(padding)));
112
+ }
113
+
114
+ return lines;
115
+ }
116
+
117
+ /**
118
+ * Format simple key-value pairs as a table
119
+ */
120
+ export function formatKeyValueTable(
121
+ data: Record<string, string | number>,
122
+ options: { keyWidth?: number; valueWidth?: number } = {}
123
+ ): string[] {
124
+ const keys = Object.keys(data);
125
+ if (keys.length === 0) {return [];}
126
+
127
+ const keyWidth = options.keyWidth || Math.max(...keys.map(k => visualWidth(k)), 0);
128
+ const valueWidth = options.valueWidth || Math.max(...Object.values(data).map(v => visualWidth(String(v))), 0);
129
+
130
+ return keys.map(key => {
131
+ const value = String(data[key]);
132
+ return `${padVisual(key, keyWidth)} ${padVisual(value, valueWidth)}`;
133
+ });
134
+ }