@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,119 @@
1
+ import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest';
2
+ import { setJsonMode, isJsonMode, useLoader, Loader } from '../loader';
3
+
4
+ // Restore json mode after each test
5
+ afterEach(() => {
6
+ setJsonMode(false);
7
+ });
8
+
9
+ describe('setJsonMode / isJsonMode', () => {
10
+ it('defaults to false', () => {
11
+ expect(isJsonMode()).toBe(false);
12
+ });
13
+
14
+ it('setJsonMode(true) flips the flag', () => {
15
+ setJsonMode(true);
16
+ expect(isJsonMode()).toBe(true);
17
+ });
18
+
19
+ it('setJsonMode(false) resets the flag', () => {
20
+ setJsonMode(true);
21
+ setJsonMode(false);
22
+ expect(isJsonMode()).toBe(false);
23
+ });
24
+ });
25
+
26
+ describe('useLoader picks up global json mode', () => {
27
+ it('creates a Loader with jsonMode=false by default', () => {
28
+ const loader = useLoader('test');
29
+ // start() should normally start the interval — but we can verify it doesn't
30
+ // write anything if we capture stdout
31
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
32
+ loader.start();
33
+ // Fake enough time for the interval to fire
34
+ vi.useFakeTimers();
35
+ vi.advanceTimersByTime(500);
36
+ vi.useRealTimers();
37
+ loader.stop();
38
+ write.mockRestore();
39
+ // No assertion needed — just verify it doesn't throw
40
+ });
41
+
42
+ it('when setJsonMode(true), useLoader returns a no-op loader', () => {
43
+ setJsonMode(true);
44
+ const loader = useLoader('loading...');
45
+
46
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
47
+ loader.start();
48
+ loader.succeed('done');
49
+ loader.fail('error');
50
+ expect(write).not.toHaveBeenCalled();
51
+ write.mockRestore();
52
+ });
53
+
54
+ it('explicit jsonMode option overrides global flag', () => {
55
+ setJsonMode(true);
56
+ // Explicit false should override the global true
57
+ const loader = useLoader('test', { jsonMode: false });
58
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
59
+ loader.succeed('done');
60
+ // succeed() writes when jsonMode=false
61
+ expect(write).toHaveBeenCalled();
62
+ write.mockRestore();
63
+ });
64
+ });
65
+
66
+ describe('Loader in json mode suppresses all output', () => {
67
+ beforeEach(() => {
68
+ vi.useFakeTimers();
69
+ });
70
+
71
+ afterEach(() => {
72
+ vi.useRealTimers();
73
+ });
74
+
75
+ it('start() does not write to stdout', () => {
76
+ const loader = new Loader({ text: 'loading...', jsonMode: true });
77
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
78
+ loader.start();
79
+ vi.advanceTimersByTime(1000);
80
+ expect(write).not.toHaveBeenCalled();
81
+ write.mockRestore();
82
+ });
83
+
84
+ it('succeed() does not write to stdout', () => {
85
+ const loader = new Loader({ text: 'loading...', jsonMode: true });
86
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
87
+ loader.start();
88
+ loader.succeed('completed');
89
+ expect(write).not.toHaveBeenCalled();
90
+ write.mockRestore();
91
+ });
92
+
93
+ it('fail() does not write to stdout', () => {
94
+ const loader = new Loader({ text: 'loading...', jsonMode: true });
95
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
96
+ loader.start();
97
+ loader.fail('failed');
98
+ expect(write).not.toHaveBeenCalled();
99
+ write.mockRestore();
100
+ });
101
+ });
102
+
103
+ describe('Loader in normal mode writes to stdout', () => {
104
+ it('succeed() writes success symbol to stdout', () => {
105
+ const loader = new Loader({ text: 'loading...', jsonMode: false });
106
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
107
+ loader.succeed('done');
108
+ expect(write).toHaveBeenCalledWith(expect.stringContaining('done'));
109
+ write.mockRestore();
110
+ });
111
+
112
+ it('fail() writes error symbol to stdout', () => {
113
+ const loader = new Loader({ text: 'loading...', jsonMode: false });
114
+ const write = vi.spyOn(process.stdout, 'write').mockReturnValue(true);
115
+ loader.fail('oops');
116
+ expect(write).toHaveBeenCalledWith(expect.stringContaining('oops'));
117
+ write.mockRestore();
118
+ });
119
+ });
@@ -0,0 +1,266 @@
1
+ import path from 'node:path';
2
+ import { promises as fsp } from 'node:fs';
3
+ import { safeColors } from './colors';
4
+ import { formatSize, formatRelativeTime, safeKeyValue } from './format';
5
+
6
+ export interface ArtifactInfo {
7
+ name: string;
8
+ path: string;
9
+ size?: number;
10
+ modified?: Date;
11
+ description: string;
12
+ }
13
+
14
+ export interface ArtifactDisplayOptions {
15
+ showSize?: boolean;
16
+ showTime?: boolean;
17
+ showDescription?: boolean;
18
+ maxItems?: number;
19
+ title?: string;
20
+ groupBy?: 'none' | 'type' | 'time';
21
+ }
22
+
23
+ /**
24
+ * Display artifacts information in CLI
25
+ */
26
+ export function displayArtifacts(
27
+ artifacts: ArtifactInfo[],
28
+ options: ArtifactDisplayOptions = {},
29
+ ): string[] {
30
+ const {
31
+ showSize = true,
32
+ showTime = true,
33
+ showDescription = false,
34
+ maxItems = 10,
35
+ title = 'Generated Artifacts',
36
+ groupBy = 'none'
37
+ } = options;
38
+
39
+ if (artifacts.length === 0) {
40
+ return [];
41
+ }
42
+
43
+ // Sort and limit artifacts
44
+ let sortedArtifacts = artifacts;
45
+
46
+ if (groupBy === 'time') {
47
+ sortedArtifacts = [...artifacts].sort(
48
+ (a, b) => (b.modified?.getTime() ?? 0) - (a.modified?.getTime() ?? 0),
49
+ );
50
+ } else if (groupBy === 'type') {
51
+ sortedArtifacts = [...artifacts].sort((a, b) => a.name.localeCompare(b.name));
52
+ } else {
53
+ sortedArtifacts = [...artifacts].sort(
54
+ (a, b) => (b.modified?.getTime() ?? 0) - (a.modified?.getTime() ?? 0),
55
+ );
56
+ }
57
+
58
+ sortedArtifacts = sortedArtifacts.slice(0, maxItems);
59
+
60
+ const lines: string[] = [];
61
+
62
+ if (title) {
63
+ lines.push(safeColors.bold(title));
64
+ }
65
+
66
+ if (groupBy === 'type') {
67
+ const grouped = sortedArtifacts.reduce((acc, artifact) => {
68
+ const type = artifact.name.split(' ')[0] || 'Other';
69
+ if (!acc[type]) {acc[type] = [];}
70
+ acc[type]!.push(artifact);
71
+ return acc;
72
+ }, {} as Record<string, ArtifactInfo[]>);
73
+
74
+ Object.entries(grouped).forEach(([type, items], index) => {
75
+ if (index > 0 || title) {
76
+ lines.push('');
77
+ }
78
+ lines.push(...safeKeyValue({ [type]: '' }, { pad: false }));
79
+ items.forEach((artifact, artifactIndex) => {
80
+ if (artifactIndex > 0) {
81
+ lines.push('');
82
+ }
83
+ lines.push(...formatArtifactLines(artifact, {
84
+ showSize,
85
+ showTime,
86
+ showDescription,
87
+ indent: 2,
88
+ }));
89
+ });
90
+ });
91
+ return lines;
92
+ }
93
+
94
+ sortedArtifacts.forEach((artifact, index) => {
95
+ if (index > 0) {
96
+ lines.push('');
97
+ }
98
+ lines.push(...formatArtifactLines(artifact, {
99
+ showSize,
100
+ showTime,
101
+ showDescription,
102
+ indent: 0,
103
+ }));
104
+ });
105
+
106
+ return lines;
107
+ }
108
+
109
+ /**
110
+ * Format a single artifact line
111
+ */
112
+ function formatArtifactLines(
113
+ artifact: ArtifactInfo,
114
+ options: {
115
+ showSize: boolean;
116
+ showTime: boolean;
117
+ showDescription: boolean;
118
+ indent: number;
119
+ },
120
+ ): string[] {
121
+ const { showSize, showTime, showDescription, indent } = options;
122
+ const relativePath = path.relative(process.cwd(), artifact.path);
123
+ const lines: string[] = [];
124
+ lines.push(
125
+ ...safeKeyValue({ [artifact.name]: relativePath }, { pad: false, indent }),
126
+ );
127
+ if (showSize && artifact.size) {
128
+ lines.push(
129
+ ...safeKeyValue({ Size: formatSize(artifact.size) }, { pad: false, indent: indent + 2 }),
130
+ );
131
+ }
132
+
133
+ if (showTime && artifact.modified) {
134
+ lines.push(
135
+ ...safeKeyValue({ Updated: formatRelativeTime(artifact.modified) }, { pad: false, indent: indent + 2 }),
136
+ );
137
+ }
138
+
139
+ if (showDescription && artifact.description) {
140
+ lines.push(
141
+ ...safeKeyValue({ Note: artifact.description }, { pad: false, indent: indent + 2 }),
142
+ );
143
+ }
144
+
145
+ return lines;
146
+ }
147
+
148
+ /**
149
+ * Display a single artifact with full details
150
+ */
151
+ export function displaySingleArtifact(artifact: ArtifactInfo, title?: string): string[] {
152
+ const relativePath = path.relative(process.cwd(), artifact.path);
153
+ const lines: string[] = [];
154
+
155
+ if (title) {
156
+ lines.push('');
157
+ lines.push(safeColors.bold(title));
158
+ }
159
+
160
+ lines.push(` ${safeColors.bold(artifact.name)}: ${safeColors.info(relativePath)}`);
161
+
162
+ if (artifact.size) {
163
+ lines.push(` Size: ${formatSize(artifact.size)}`);
164
+ }
165
+
166
+ if (artifact.modified) {
167
+ lines.push(` Modified: ${formatRelativeTime(artifact.modified)}`);
168
+ }
169
+
170
+ if (artifact.description) {
171
+ lines.push(` Description: ${artifact.description}`);
172
+ }
173
+
174
+ return lines;
175
+ }
176
+
177
+ /**
178
+ * Display artifacts in a compact format (for status-like displays)
179
+ */
180
+ export function displayArtifactsCompact(
181
+ artifacts: ArtifactInfo[],
182
+ options: {
183
+ maxItems?: number;
184
+ showSize?: boolean;
185
+ sortByTime?: boolean;
186
+ showTime?: boolean;
187
+ title?: string;
188
+ } = {},
189
+ ): string[] {
190
+ const {
191
+ maxItems = 5,
192
+ showSize = true,
193
+ sortByTime = true,
194
+ showTime = true,
195
+ title = 'Artifacts',
196
+ } = options;
197
+
198
+ if (artifacts.length === 0) {
199
+ return [];
200
+ }
201
+
202
+ const sortedArtifacts = (sortByTime
203
+ ? [...artifacts].sort((a, b) => (b.modified?.getTime() ?? 0) - (a.modified?.getTime() ?? 0))
204
+ : artifacts.slice()
205
+ ).slice(0, maxItems);
206
+
207
+ const lines: string[] = [];
208
+ lines.push(safeColors.bold(title));
209
+
210
+ sortedArtifacts.forEach((artifact, index) => {
211
+ if (index > 0) {
212
+ lines.push('');
213
+ }
214
+ lines.push(
215
+ ...formatArtifactLines(artifact, {
216
+ showSize,
217
+ showTime,
218
+ showDescription: false,
219
+ indent: 0,
220
+ }),
221
+ );
222
+ });
223
+
224
+ return lines;
225
+ }
226
+
227
+ /**
228
+ * Discover artifacts in a directory based on patterns
229
+ *
230
+ * @param baseDir - Base directory to search for artifacts
231
+ * @param patterns - Array of artifact patterns to search for
232
+ * @returns Array of discovered artifacts
233
+ *
234
+ * @example
235
+ * ```typescript
236
+ * const artifacts = await discoverArtifacts('.kb/mind', [
237
+ * { name: 'Index', pattern: 'index.json', description: 'Main index' },
238
+ * { name: 'API Index', pattern: 'api-index.json', description: 'API index' },
239
+ * ]);
240
+ * ```
241
+ */
242
+ export async function discoverArtifacts(
243
+ baseDir: string,
244
+ patterns: Array<{ name: string; pattern: string; description?: string }>
245
+ ): Promise<ArtifactInfo[]> {
246
+ const artifacts: ArtifactInfo[] = [];
247
+
248
+ for (const artifact of patterns) {
249
+ const artifactPath = path.join(baseDir, artifact.pattern);
250
+ try {
251
+ await fsp.access(artifactPath);
252
+ const stats = await fsp.stat(artifactPath);
253
+ artifacts.push({
254
+ name: artifact.name,
255
+ path: artifactPath,
256
+ size: stats.size,
257
+ modified: stats.mtime,
258
+ description: artifact.description || ''
259
+ });
260
+ } catch {
261
+ // Skip if file doesn't exist
262
+ }
263
+ }
264
+
265
+ return artifacts;
266
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Automatic CLI discovery and registration
3
+ */
4
+
5
+ import { MultiCLISuggestions, type CLIPackage } from './multi-cli-suggestions';
6
+ import { extractCommandIds } from './manifest-parser';
7
+
8
+ export interface CLIDiscoveryOptions {
9
+ /**
10
+ * Root directory to search for CLI packages
11
+ */
12
+ rootDir?: string;
13
+
14
+ /**
15
+ * Whether to include dev dependencies
16
+ */
17
+ includeDev?: boolean;
18
+
19
+ /**
20
+ * Custom package patterns to search for
21
+ */
22
+ patterns?: string[];
23
+ }
24
+
25
+ /**
26
+ * Discover CLI packages in a workspace
27
+ */
28
+ export async function discoverCLIPackages(
29
+ options: CLIDiscoveryOptions = {}
30
+ ): Promise<CLIPackage[]> {
31
+ const packages: CLIPackage[] = [];
32
+ const rootDir = options.rootDir || process.cwd();
33
+
34
+ // Common patterns for CLI packages
35
+ const patterns = options.patterns || [
36
+ '**/cli.manifest.ts',
37
+ '**/cli.manifest.js',
38
+ '**/src/cli.manifest.ts',
39
+ '**/src/cli.manifest.js'
40
+ ];
41
+
42
+ try {
43
+ const { glob } = await import('glob');
44
+
45
+ for (const pattern of patterns) {
46
+ const files = await glob(pattern, { cwd: rootDir });
47
+
48
+ for (const file of files) {
49
+ try {
50
+ const manifestPath = `${rootDir}/${file}`;
51
+ const manifest = await import(manifestPath);
52
+
53
+ if (manifest.commands && Array.isArray(manifest.commands)) {
54
+ // Extract package info from path
55
+ const pathParts = file.split('/');
56
+ const packageName = pathParts[pathParts.length - 3] || 'unknown';
57
+ const group = packageName.replace(/-cli$/, '').replace(/^kb-labs-/, '');
58
+
59
+ // Determine priority based on package type
60
+ let priority = 50;
61
+ if (group === 'devlink') {priority = 100;}
62
+ else if (group === 'mind') {priority = 80;}
63
+ else if (group === 'tox') {priority = 70;}
64
+ else if (group === 'profiles') {priority = 60;}
65
+ else if (group === 'ai-review') {priority = 50;}
66
+
67
+ packages.push({
68
+ name: packageName,
69
+ group,
70
+ commands: manifest.commands,
71
+ priority
72
+ });
73
+ }
74
+ } catch (error) {
75
+ // Skip invalid manifests
76
+ console.warn(`Failed to load manifest: ${file}`, error);
77
+ }
78
+ }
79
+ }
80
+ } catch (error) {
81
+ console.warn('Failed to discover CLI packages:', error);
82
+ }
83
+
84
+ return packages;
85
+ }
86
+
87
+ /**
88
+ * Create a multi-CLI suggestions instance with auto-discovery
89
+ */
90
+ export async function createAutoDiscoveredCLISuggestions(
91
+ options: CLIDiscoveryOptions = {}
92
+ ): Promise<MultiCLISuggestions> {
93
+ const manager = new MultiCLISuggestions();
94
+ const packages = await discoverCLIPackages(options);
95
+
96
+ for (const pkg of packages) {
97
+ manager.registerPackage(pkg);
98
+ }
99
+
100
+ return manager;
101
+ }
102
+
103
+ /**
104
+ * Get command registry for a specific workspace
105
+ */
106
+ export async function getWorkspaceCommandRegistry(
107
+ rootDir: string = process.cwd()
108
+ ): Promise<{ registry: any; packages: CLIPackage[] }> {
109
+ const packages = await discoverCLIPackages({ rootDir });
110
+ const allCommands: string[] = [];
111
+
112
+ for (const pkg of packages) {
113
+ allCommands.push(...extractCommandIds(pkg.commands));
114
+ }
115
+
116
+ const { createCommandRegistry } = await import('./command-suggestions');
117
+ const registry = createCommandRegistry(allCommands);
118
+
119
+ return { registry, packages };
120
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Minimalist color utilities for CLI output
3
+ * Uses strategic color application - only for status, not decoration
4
+ */
5
+
6
+ const CSI = '\x1b[';
7
+ const RESET = '\x1b[0m';
8
+
9
+ const createColor =
10
+ (...codes: Array<number | string>) =>
11
+ (text: string): string =>
12
+ `${CSI}${codes.join(';')}m${text}${RESET}`;
13
+
14
+ const accentBlue = '38;5;39';
15
+ const accentViolet = '38;5;99';
16
+ const accentTeal = '38;5;51';
17
+ const accentIndigo = '38;5;63';
18
+ const neutral = 37;
19
+ const neutralMuted = 90;
20
+
21
+ export const colors = {
22
+ // Semantic colors
23
+ success: createColor(32),
24
+ error: createColor(31),
25
+ warning: createColor(33),
26
+ info: createColor(36),
27
+
28
+ // Accent palette (reused across CLI)
29
+ primary: createColor(accentBlue),
30
+ accent: createColor(accentViolet),
31
+ highlight: createColor(accentTeal),
32
+ secondary: createColor(accentIndigo),
33
+ emphasis: createColor('38;5;117'),
34
+ muted: createColor(neutralMuted),
35
+ foreground: createColor(neutral),
36
+
37
+ // Formatting helpers
38
+ dim: createColor(2),
39
+ bold: createColor(1),
40
+ underline: createColor(4),
41
+ inverse: createColor(7),
42
+ };
43
+
44
+ // Base symbols without emojis
45
+ const symbolCharacters = {
46
+ success: 'OK',
47
+ error: 'ERR',
48
+ warning: 'WARN',
49
+ info: 'ℹ',
50
+ bullet: '•',
51
+ clock: 'TIME',
52
+ folder: 'DIR',
53
+ package: '›',
54
+ pointer: '›',
55
+ section: '│',
56
+ };
57
+
58
+ export const symbols = {
59
+ success: colors.success(symbolCharacters.success),
60
+ error: colors.error(symbolCharacters.error),
61
+ warning: colors.warning(symbolCharacters.warning),
62
+ info: colors.info(symbolCharacters.info),
63
+ bullet: colors.muted(symbolCharacters.bullet),
64
+ clock: colors.info(symbolCharacters.clock),
65
+ folder: colors.primary(symbolCharacters.folder),
66
+ package: colors.accent(symbolCharacters.package),
67
+ pointer: colors.primary(symbolCharacters.pointer),
68
+ section: colors.primary(symbolCharacters.section),
69
+ };
70
+
71
+ // Check if colors are supported
72
+ const isTruthyEnv = (value: string | undefined): boolean => {
73
+ if (!value) {return false;}
74
+ const normalized = value.trim().toLowerCase();
75
+ return normalized !== '' && normalized !== '0' && normalized !== 'false' && normalized !== 'off';
76
+ };
77
+
78
+ export const supportsColor = (() => {
79
+ if (typeof process === 'undefined') {return false;}
80
+
81
+ const forceColor = process.env.FORCE_COLOR;
82
+ if (isTruthyEnv(process.env.NO_COLOR)) {
83
+ return false;
84
+ }
85
+ if (isTruthyEnv(forceColor)) {
86
+ return true;
87
+ }
88
+
89
+ if (!process.stdout) {return false;}
90
+
91
+ if (process.stdout.isTTY === false) {
92
+ return false;
93
+ }
94
+
95
+ return true;
96
+ })();
97
+
98
+ // Color functions that respect NO_COLOR
99
+ const passthrough =
100
+ (fn: (text: string) => string) =>
101
+ (text: string): string =>
102
+ supportsColor ? fn(text) : text;
103
+
104
+ export const safeColors = {
105
+ success: passthrough(colors.success),
106
+ error: passthrough(colors.error),
107
+ warning: passthrough(colors.warning),
108
+ info: passthrough(colors.info),
109
+ accent: passthrough(colors.accent),
110
+ primary: passthrough(colors.primary),
111
+ highlight: passthrough(colors.highlight),
112
+ secondary: passthrough(colors.secondary),
113
+ emphasis: passthrough(colors.emphasis),
114
+ foreground: passthrough(colors.foreground),
115
+ muted: passthrough(colors.muted),
116
+ dim: passthrough(colors.dim),
117
+ bold: passthrough(colors.bold),
118
+ underline: passthrough(colors.underline),
119
+ inverse: passthrough(colors.inverse),
120
+ };
121
+
122
+ export const safeSymbols = {
123
+ success: supportsColor ? symbols.success : '✓',
124
+ error: supportsColor ? symbols.error : '✗',
125
+ warning: supportsColor ? symbols.warning : '⚠',
126
+ info: supportsColor ? symbols.info : '→',
127
+ bullet: supportsColor ? symbols.bullet : '•',
128
+ clock: supportsColor ? symbols.clock : 'time',
129
+ folder: supportsColor ? symbols.folder : 'dir',
130
+ package: supportsColor ? symbols.package : '›',
131
+ pointer: supportsColor ? symbols.pointer : '>',
132
+ section: supportsColor ? symbols.section : '|',
133
+ // Box-drawing characters for modern side border
134
+ separator: '─', // Horizontal line
135
+ border: '│', // Vertical line
136
+ topLeft: '┌', // Top-left corner
137
+ topRight: '┐', // Top-right corner
138
+ bottomLeft: '└', // Bottom-left corner
139
+ bottomRight: '┘', // Bottom-right corner
140
+ leftT: '├', // Left T-junction
141
+ rightT: '┤', // Right T-junction
142
+ };