@haposoft/cafekit 0.8.12 → 0.8.13
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/package.json +1 -1
- package/src/claude/hooks/docs-sync.cjs +3 -1
- package/src/claude/hooks/lib/color.cjs +12 -5
- package/src/claude/hooks/lib/config.cjs +31 -23
- package/src/claude/hooks/lib/skill-router-routes.cjs +7 -7
- package/src/claude/hooks/skill-router.cjs +2 -2
- package/src/claude/hooks/usage.cjs +15 -8
- package/src/claude/skills/ai-multimodal/SKILL.md +1 -1
- package/src/claude/status.cjs +4 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,13 +15,15 @@ try {
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { execSync } = require('child_process');
|
|
18
|
+
const { loadConfig } = require('./lib/config.cjs');
|
|
18
19
|
|
|
19
20
|
// Đọc stdin theo chuẩn hook
|
|
20
21
|
const stdin = fs.readFileSync(0, 'utf8').trim();
|
|
21
22
|
const payload = stdin ? JSON.parse(stdin) : {};
|
|
22
23
|
const cwd = payload.cwd || process.cwd();
|
|
24
|
+
const config = loadConfig({ cwd, includeProject: false, includeAssertions: false, includeLocale: false });
|
|
23
25
|
|
|
24
|
-
const docsDir = path.join(cwd, 'docs');
|
|
26
|
+
const docsDir = path.join(cwd, config.paths?.docs || 'docs');
|
|
25
27
|
|
|
26
28
|
// Xác định dự án đã có cốt lõi code hay chưa?
|
|
27
29
|
const hasCode = fs.existsSync(path.join(cwd, 'src')) ||
|
|
@@ -16,14 +16,20 @@ const YELLOW = '\x1b[33m';
|
|
|
16
16
|
const MAGENTA = '\x1b[35m';
|
|
17
17
|
const CYAN = '\x1b[36m';
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
// Claude Code statusline runs via pipe but output displays in TTY - default to true
|
|
21
|
-
const shouldUseColor = (() => {
|
|
19
|
+
function detectColorDefault() {
|
|
22
20
|
if (process.env.NO_COLOR) return false;
|
|
23
21
|
if (process.env.FORCE_COLOR) return true;
|
|
24
22
|
// Default true for statusline context (Claude Code handles TTY display)
|
|
25
23
|
return true;
|
|
26
|
-
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Detect color support at module load (cached)
|
|
27
|
+
// Claude Code statusline runs via pipe but output displays in TTY - default to true
|
|
28
|
+
let shouldUseColor = detectColorDefault();
|
|
29
|
+
|
|
30
|
+
function setColorEnabled(enabled) {
|
|
31
|
+
shouldUseColor = enabled === false ? false : detectColorDefault();
|
|
32
|
+
}
|
|
27
33
|
|
|
28
34
|
// Detect 256-color support via COLORTERM
|
|
29
35
|
const has256Color = (() => {
|
|
@@ -90,6 +96,7 @@ module.exports = {
|
|
|
90
96
|
dim,
|
|
91
97
|
getContextColor,
|
|
92
98
|
coloredBar,
|
|
93
|
-
shouldUseColor,
|
|
99
|
+
get shouldUseColor() { return shouldUseColor; },
|
|
100
|
+
setColorEnabled,
|
|
94
101
|
has256Color
|
|
95
102
|
};
|
|
@@ -9,11 +9,9 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const os = require('os');
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const GLOBAL_CONFIG_PATH = path.join(os.homedir(), '.claude', '.ck.json');
|
|
12
|
+
const RUNTIME_CONFIG_PATH = '.claude/runtime.json';
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
const CONFIG_PATH = LOCAL_CONFIG_PATH;
|
|
14
|
+
const CONFIG_PATH = RUNTIME_CONFIG_PATH;
|
|
17
15
|
|
|
18
16
|
const DEFAULT_CONFIG = {
|
|
19
17
|
plan: {
|
|
@@ -61,6 +59,7 @@ const DEFAULT_CONFIG = {
|
|
|
61
59
|
},
|
|
62
60
|
assertions: [],
|
|
63
61
|
statusline: 'full',
|
|
62
|
+
statuslineColors: true,
|
|
64
63
|
hooks: {
|
|
65
64
|
'session-init': true,
|
|
66
65
|
'subagent-init': true,
|
|
@@ -81,8 +80,8 @@ const DEFAULT_CONFIG = {
|
|
|
81
80
|
* Arrays are replaced entirely (not concatenated) to avoid duplicate entries
|
|
82
81
|
*
|
|
83
82
|
* IMPORTANT: Empty objects {} are treated as "inherit from parent", not "replace with empty".
|
|
84
|
-
* This allows
|
|
85
|
-
*
|
|
83
|
+
* This allows runtime config to leave hooks as {} (inherit defaults)
|
|
84
|
+
* without resetting all default hook toggles.
|
|
86
85
|
*
|
|
87
86
|
* @param {Object} target - Base object
|
|
88
87
|
* @param {Object} source - Object to merge (takes precedence)
|
|
@@ -464,36 +463,39 @@ function sanitizeConfig(config, projectRoot) {
|
|
|
464
463
|
}
|
|
465
464
|
|
|
466
465
|
/**
|
|
467
|
-
* Load config with cascading resolution: DEFAULT →
|
|
466
|
+
* Load config with cascading resolution: DEFAULT → runtime
|
|
468
467
|
*
|
|
469
468
|
* Resolution order (each layer overrides the previous):
|
|
470
469
|
* 1. DEFAULT_CONFIG (hardcoded defaults)
|
|
471
|
-
* 2.
|
|
472
|
-
* 3. Local config (./.claude/.ck.json) - project-specific overrides
|
|
470
|
+
* 2. Runtime config (./.claude/runtime.json) - installed CafeKit runtime config
|
|
473
471
|
*
|
|
474
472
|
* @param {Object} options - Options for config loading
|
|
475
473
|
* @param {boolean} options.includeProject - Include project section (default: true)
|
|
476
474
|
* @param {boolean} options.includeAssertions - Include assertions (default: true)
|
|
477
475
|
* @param {boolean} options.includeLocale - Include locale section (default: true)
|
|
476
|
+
* @param {string} options.cwd - Project root for local config lookup (default: process.cwd())
|
|
478
477
|
*/
|
|
479
478
|
function loadConfig(options = {}) {
|
|
480
|
-
const {
|
|
481
|
-
|
|
479
|
+
const {
|
|
480
|
+
includeProject = true,
|
|
481
|
+
includeAssertions = true,
|
|
482
|
+
includeLocale = true,
|
|
483
|
+
cwd = process.cwd()
|
|
484
|
+
} = options;
|
|
485
|
+
const projectRoot = cwd;
|
|
482
486
|
|
|
483
|
-
// Load
|
|
484
|
-
const
|
|
485
|
-
const localConfig = loadConfigFromPath(LOCAL_CONFIG_PATH);
|
|
487
|
+
// Load config from the installed CafeKit runtime config.
|
|
488
|
+
const runtimeConfig = loadConfigFromPath(path.join(projectRoot, RUNTIME_CONFIG_PATH));
|
|
486
489
|
|
|
487
490
|
// No config files found - use defaults
|
|
488
|
-
if (!
|
|
491
|
+
if (!runtimeConfig) {
|
|
489
492
|
return getDefaultConfig(includeProject, includeAssertions, includeLocale);
|
|
490
493
|
}
|
|
491
494
|
|
|
492
495
|
try {
|
|
493
|
-
// Deep merge: DEFAULT →
|
|
496
|
+
// Deep merge: DEFAULT → runtime (runtime wins)
|
|
494
497
|
let merged = deepMerge({}, DEFAULT_CONFIG);
|
|
495
|
-
if (
|
|
496
|
-
if (localConfig) merged = deepMerge(merged, localConfig);
|
|
498
|
+
if (runtimeConfig) merged = deepMerge(merged, runtimeConfig);
|
|
497
499
|
|
|
498
500
|
// Build result with optional sections
|
|
499
501
|
const result = {
|
|
@@ -523,6 +525,7 @@ function loadConfig(options = {}) {
|
|
|
523
525
|
result.hooks = merged.hooks || DEFAULT_CONFIG.hooks;
|
|
524
526
|
// Statusline mode
|
|
525
527
|
result.statusline = merged.statusline || 'full';
|
|
528
|
+
result.statuslineColors = merged.statuslineColors !== false;
|
|
526
529
|
|
|
527
530
|
return sanitizeConfig(result, projectRoot);
|
|
528
531
|
} catch (e) {
|
|
@@ -541,7 +544,8 @@ function getDefaultConfig(includeProject = true, includeAssertions = true, inclu
|
|
|
541
544
|
codingLevel: -1, // Default: disabled (no injection, saves tokens)
|
|
542
545
|
skills: { ...DEFAULT_CONFIG.skills },
|
|
543
546
|
hooks: { ...DEFAULT_CONFIG.hooks },
|
|
544
|
-
statusline: 'full'
|
|
547
|
+
statusline: 'full',
|
|
548
|
+
statuslineColors: true
|
|
545
549
|
};
|
|
546
550
|
if (includeLocale) {
|
|
547
551
|
result.locale = { ...DEFAULT_CONFIG.locale };
|
|
@@ -790,8 +794,13 @@ function extractTaskListId(resolved) {
|
|
|
790
794
|
* @param {string} hookName - Hook name (script basename without .cjs)
|
|
791
795
|
* @returns {boolean} Whether hook is enabled
|
|
792
796
|
*/
|
|
793
|
-
function isHookEnabled(hookName) {
|
|
794
|
-
const config = loadConfig({
|
|
797
|
+
function isHookEnabled(hookName, options = {}) {
|
|
798
|
+
const config = loadConfig({
|
|
799
|
+
includeProject: false,
|
|
800
|
+
includeAssertions: false,
|
|
801
|
+
includeLocale: false,
|
|
802
|
+
cwd: options.cwd
|
|
803
|
+
});
|
|
795
804
|
const hooks = config.hooks || {};
|
|
796
805
|
// Return true if undefined (default enabled), otherwise return the boolean value
|
|
797
806
|
return hooks[hookName] !== false;
|
|
@@ -799,8 +808,7 @@ function isHookEnabled(hookName) {
|
|
|
799
808
|
|
|
800
809
|
module.exports = {
|
|
801
810
|
CONFIG_PATH,
|
|
802
|
-
|
|
803
|
-
GLOBAL_CONFIG_PATH,
|
|
811
|
+
RUNTIME_CONFIG_PATH,
|
|
804
812
|
DEFAULT_CONFIG,
|
|
805
813
|
INVALID_FILENAME_CHARS,
|
|
806
814
|
deepMerge,
|
|
@@ -27,8 +27,8 @@ const ROUTES = [
|
|
|
27
27
|
negative: ['bug', 'debug', 'review', 'test only', 'commit'],
|
|
28
28
|
}),
|
|
29
29
|
route('hapo:test', 'test, verification, QA, or runtime validation', 70, {
|
|
30
|
-
strong: ['unit test', 'integration test', 'e2e', 'playwright', 'coverage', 'kiểm thử', 'kiem thu', '単体テスト', '結合テスト', 'カバレッジ', 'テストして'],
|
|
31
|
-
medium: ['test', 'tests', 'testing', 'qa', 'verify', 'verification', 'kiểm tra chạy', 'kiem tra chay', 'xác minh', 'xac minh', 'テスト', '検証', '確認', '動作確認'],
|
|
30
|
+
strong: ['unit test', 'integration test', 'e2e', 'playwright', 'coverage', 'test all', 'run all tests', 'test toàn bộ', 'test toan bo', 'kiểm thử', 'kiem thu', 'kiểm tra toàn bộ', 'kiem tra toan bo', '単体テスト', '結合テスト', '全体テスト', 'カバレッジ', 'テストして'],
|
|
31
|
+
medium: ['test', 'tests', 'testing', 'qa', 'verify', 'verification', 'kiểm tra chạy', 'kiem tra chay', 'xác minh', 'xac minh', 'テスト', '検証', '確認', '動作確認', '全体確認'],
|
|
32
32
|
weak: ['assert', 'runtime proof', 'manual qa', 'end to end', 'smoke test', 'アサート', 'スモークテスト'],
|
|
33
33
|
negative: ['spec', 'requirements', 'commit', 'push'],
|
|
34
34
|
}),
|
|
@@ -45,8 +45,8 @@ const ROUTES = [
|
|
|
45
45
|
negative: ['deploy', 'test', 'review only'],
|
|
46
46
|
}),
|
|
47
47
|
route('hapo:inspect', 'codebase discovery, file search, structure scan, or locating implementation areas', 64, {
|
|
48
|
-
strong: ['inspect', 'codebase scan', 'scan codebase', 'scan source', 'file discovery', 'find files', 'locate files', 'xem source', 'xem codebase', 'quét source', 'quet source', 'quét codebase', 'quet codebase', 'kiểm tra cấu trúc', 'kiem tra cau truc', 'コード構造', 'ソース確認', 'コードベース確認', 'ファイル探索', '構造を確認'],
|
|
49
|
-
medium: ['search files', 'find where', 'where is', 'project structure', 'code structure', 'repo structure', 'tìm file', 'tim file', 'tìm trong source', 'tim trong source', 'cấu trúc project', 'cau truc project', 'ở đâu', 'o dau', '関連ファイル', 'どこにある', 'プロジェクト構造', 'リポジトリ構造', '探して'],
|
|
48
|
+
strong: ['inspect', 'codebase scan', 'scan codebase', 'scan source', 'file discovery', 'find files', 'locate files', 'xem source', 'xem codebase', 'kiểm tra source', 'kiem tra source', 'kiểm tra source code', 'kiem tra source code', 'quét source', 'quet source', 'quét codebase', 'quet codebase', 'kiểm tra cấu trúc', 'kiem tra cau truc', 'コード構造', 'ソース確認', 'コードベース確認', 'ファイル探索', '構造を確認'],
|
|
49
|
+
medium: ['search files', 'find where', 'where is', 'project structure', 'code structure', 'repo structure', 'source code', 'tìm file', 'tim file', 'tìm trong source', 'tim trong source', 'cấu trúc project', 'cau truc project', 'ở đâu', 'o dau', 'nằm đâu', 'nam dau', '関連ファイル', 'どこにある', 'プロジェクト構造', 'リポジトリ構造', '探して'],
|
|
50
50
|
weak: ['scan', 'inspect code', 'explore code', 'xem qua', 'xem giúp', '調べて', '確認して'],
|
|
51
51
|
negative: ['bug', 'error', 'lỗi', 'loi', 'fail', 'failure', 'production', 'hotfix', 'fix', 'sửa', 'sua', 'debug', 'develop', 'implement', 'test', 'commit', 'push', 'slide', 'pptx'],
|
|
52
52
|
}),
|
|
@@ -63,8 +63,8 @@ const ROUTES = [
|
|
|
63
63
|
negative: ['backend', 'api', 'database'],
|
|
64
64
|
}),
|
|
65
65
|
route('hapo:react-best-practices', 'React and Next.js performance patterns, rerender optimization, and Vercel best practices', 60, {
|
|
66
|
-
strong: ['react best practices', 'next.js best practices', 'vercel react best practices', 'react performance', 'next.js performance', 'optimize react', 'optimize next.js', 'tối ưu react', 'toi uu react', 'tối ưu next.js', 'toi uu next.js', 'reactベストプラクティス', 'next.jsベストプラクティス', 'react最適化', 'next.js最適化', 'react性能'],
|
|
67
|
-
medium: ['bundle optimization', 'bundle size', 'rerender optimization', 're-render optimization', 'data fetching', 'server component', 'client component', 'suspense', 'hydration', 'waterfall', 'usememo', 'usecallback', 'react cache', 'tối ưu rerender', 'toi uu rerender', 'tối ưu bundle', 'toi uu bundle', 'バンドル最適化', '再レンダー最適化', 'データ取得', 'サーバーコンポーネント', 'クライアントコンポーネント', 'ハイドレーション'],
|
|
66
|
+
strong: ['react best practices', 'next.js best practices', 'vercel react best practices', 'react performance', 'next.js performance', 'optimize react', 'optimize next.js', 'rerender nhiều', 'rerender nhieu', 'nhiều rerender', 'nhieu rerender', 'tối ưu react', 'toi uu react', 'tối ưu next.js', 'toi uu next.js', 'reactベストプラクティス', 'next.jsベストプラクティス', 'react最適化', 'next.js最適化', 'react性能'],
|
|
67
|
+
medium: ['bundle optimization', 'bundle size', 'rerender optimization', 're-render optimization', 'data fetching', 'server component', 'client component', 'suspense', 'hydration', 'waterfall', 'usememo', 'usecallback', 'react cache', 'optimize', 'optimization', 'tối ưu', 'toi uu', 'tối ưu rerender', 'toi uu rerender', 'tối ưu bundle', 'toi uu bundle', 'バンドル最適化', '再レンダー最適化', '最適化', 'データ取得', 'サーバーコンポーネント', 'クライアントコンポーネント', 'ハイドレーション'],
|
|
68
68
|
weak: ['react', 'next.js', 'memo', 'rerender', 're-render', 'render performance', 'component performance', 'waterfalls', 'lazy state', 'dynamic import', 'react pattern', 'next.js pattern', 'レンダー性能', 'コンポーネント性能', '動的インポート'],
|
|
69
69
|
negative: ['backend', 'api', 'database', 'slide', 'pptx', 'commit', 'push'],
|
|
70
70
|
}),
|
|
@@ -118,7 +118,7 @@ const ROUTES = [
|
|
|
118
118
|
}),
|
|
119
119
|
route('hapo:agent-browser', 'browser automation with snapshot refs, web interaction, recording, or Browserbase cloud browser workflows', 45, {
|
|
120
120
|
strong: ['agent-browser', 'browser automation', 'web automation', 'browserbase', 'cloud browser', 'snapshot refs', 'browser snapshot', 'automate browser', 'tự động trình duyệt', 'tu dong trinh duyet', 'tự động thao tác trình duyệt', 'tu dong thao tac trinh duyet', 'ブラウザ自動化', 'クラウドブラウザ', 'ブラウザ操作', 'ブラウザスナップショット'],
|
|
121
|
-
medium: ['open url', 'navigate site', 'click in browser', 'fill form in browser', 'record browser', 'browser session', 'multi tab', 'browser test session', 'mở website', 'mo website', 'truy cập website', 'truy cap website', 'click trên web', 'click tren web', 'điền form web', 'dien form web', 'サイトを開く', 'ブラウザで開く', 'フォーム入力', 'クリック操作', '録画'],
|
|
121
|
+
medium: ['open url', 'navigate site', 'click in browser', 'fill form in browser', 'record browser', 'browser session', 'multi tab', 'browser test session', 'mở website', 'mo website', 'truy cập website', 'truy cap website', 'click trên web', 'click tren web', 'điền form web', 'dien form web', 'サイトを開く', 'ブラウザで開く', 'ブラウザで', 'フォーム入力', 'フォーム入力を自動化', '自動化して', 'クリック操作', '録画'],
|
|
122
122
|
weak: ['click button', 'fill form', 'open site', 'web session', 'browser ref', 'viewport', 'cookies', 'localstorage', 'ボタンをクリック', 'ビューポート', 'クッキー'],
|
|
123
123
|
negative: ['attached screenshot', 'ảnh đính kèm', 'anh dinh kem', '画像添付', 'source code', 'codebase', 'commit', 'push', 'pptx', 'pdf'],
|
|
124
124
|
}),
|
|
@@ -38,13 +38,13 @@ try {
|
|
|
38
38
|
return trimmed.startsWith('/') || /^hapo:[a-z-]+/i.test(trimmed);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (!isHookEnabled('skill-router')) process.exit(0);
|
|
42
|
-
|
|
43
41
|
const stdin = fs.readFileSync(0, 'utf8').trim();
|
|
44
42
|
if (!stdin) process.exit(0);
|
|
45
43
|
|
|
46
44
|
const payload = JSON.parse(stdin);
|
|
45
|
+
const cwd = payload.cwd || process.cwd();
|
|
47
46
|
const prompt = payload.prompt || '';
|
|
47
|
+
if (!isHookEnabled('skill-router', { cwd })) process.exit(0);
|
|
48
48
|
if (!prompt || isExplicitCommand(prompt)) process.exit(0);
|
|
49
49
|
|
|
50
50
|
const route = findRoute(prompt);
|
|
@@ -21,14 +21,15 @@ try {
|
|
|
21
21
|
const os = require("os");
|
|
22
22
|
const { execSync } = require("child_process");
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
24
|
+
function readRuntime(cwd) {
|
|
25
|
+
try {
|
|
26
|
+
const runtimePath = path.join(cwd, '.claude', 'runtime.json');
|
|
27
|
+
if (fs.existsSync(runtimePath)) {
|
|
28
|
+
return JSON.parse(fs.readFileSync(runtimePath, 'utf-8'));
|
|
29
|
+
}
|
|
30
|
+
} catch { /* fail-open */ }
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
32
33
|
|
|
33
34
|
// Cache configuration
|
|
34
35
|
const USAGE_CACHE_FILE = path.join(os.tmpdir(), "ck-usage-limits-cache.json");
|
|
@@ -151,6 +152,12 @@ async function main() {
|
|
|
151
152
|
} catch {}
|
|
152
153
|
|
|
153
154
|
const input = JSON.parse(inputStr || "{}");
|
|
155
|
+
const cwd = input.cwd || process.cwd();
|
|
156
|
+
const runtime = readRuntime(cwd);
|
|
157
|
+
if (runtime.usage?.enabled === false) {
|
|
158
|
+
console.log(JSON.stringify(result));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
154
161
|
|
|
155
162
|
// Detect hook type
|
|
156
163
|
const isUserPrompt = typeof input.prompt === "string";
|
|
@@ -35,7 +35,7 @@ export GEMINI_API_KEY_2="key2" # auto-rotates on rate limit
|
|
|
35
35
|
|
|
36
36
|
**Verify setup**: `python scripts/check_setup.py`
|
|
37
37
|
**Analyze media**: `python scripts/gemini_batch_process.py --files <file> --task <analyze|transcribe|extract>`
|
|
38
|
-
- TIP: When you're asked to analyze an image, check if `gemini` command is available, then use `echo "<prompt to analyze image>" | gemini -y -m <gemini.model>` command (read model from
|
|
38
|
+
- TIP: When you're asked to analyze an image, check if `gemini` command is available, then use `echo "<prompt to analyze image>" | gemini -y -m <gemini.model>` command (read model from `.claude/runtime.json`: `gemini.model`). If `gemini` command is not available, use `python scripts/gemini_batch_process.py --files <file> --task analyze` command.
|
|
39
39
|
|
|
40
40
|
> **Stdin support**: Pipe files via stdin for Gemini analysis (auto-detects PNG/JPG/PDF/WAV/MP3).
|
|
41
41
|
|
package/src/claude/status.cjs
CHANGED
|
@@ -14,7 +14,8 @@ const fs = require('fs');
|
|
|
14
14
|
const path = require('path');
|
|
15
15
|
|
|
16
16
|
// Import modular components
|
|
17
|
-
const
|
|
17
|
+
const colors = require('./hooks/lib/color.cjs');
|
|
18
|
+
const { green, yellow, red, cyan, magenta, dim, coloredBar, RESET } = colors;
|
|
18
19
|
const { parseTranscript } = require('./hooks/lib/parser.cjs');
|
|
19
20
|
const { countConfigs } = require('./hooks/lib/counter.cjs');
|
|
20
21
|
const { loadConfig } = require('./hooks/lib/config.cjs');
|
|
@@ -358,7 +359,7 @@ function render(ctx, singleLineMode = false) {
|
|
|
358
359
|
|
|
359
360
|
// Output all lines with non-breaking spaces for alignment
|
|
360
361
|
for (const line of lines) {
|
|
361
|
-
const outputLine = shouldUseColor ? `${RESET}${line.replace(/ /g, '\u00A0')}` : line;
|
|
362
|
+
const outputLine = colors.shouldUseColor ? `${RESET}${line.replace(/ /g, '\u00A0')}` : line;
|
|
362
363
|
console.log(outputLine);
|
|
363
364
|
}
|
|
364
365
|
}
|
|
@@ -509,6 +510,7 @@ async function main() {
|
|
|
509
510
|
// Load config and get statusline mode
|
|
510
511
|
const config = loadConfig({ includeProject: false, includeAssertions: false, includeLocale: false });
|
|
511
512
|
const statuslineMode = config.statusline || 'full';
|
|
513
|
+
colors.setColorEnabled(config.statuslineColors !== false);
|
|
512
514
|
|
|
513
515
|
// Render based on mode
|
|
514
516
|
switch (statuslineMode) {
|