@kb-labs/shared 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,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
|
+
};
|