claude-skill-lord 2.0.2 → 2.0.4

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/hooks/hooks.json CHANGED
@@ -1,5 +1,11 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "includeCoAuthoredBy": false,
4
+ "statusLine": {
5
+ "type": "command",
6
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/statusline.js\"",
7
+ "padding": 0
8
+ },
3
9
  "hooks": {
4
10
  "PreToolUse": [
5
11
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-skill-lord",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Curated Claude Code plugin — 44 agents, 170 skills, 115 commands, 13 language rules with intelligent skill routing",
5
5
  "author": {
6
6
  "name": "Dong Anh",
@@ -51,7 +51,11 @@
51
51
  "CLAUDE.md",
52
52
  "README.md",
53
53
  "LICENSE",
54
- "LICENSE-ui-ux-pro-max.txt"
54
+ "LICENSE-ui-ux-pro-max.txt",
55
+ ".env.example",
56
+ ".repomixignore",
57
+ ".commitlintrc.json",
58
+ ".releaserc.json"
55
59
  ],
56
60
  "engines": {
57
61
  "node": ">=18.0.0"
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Custom Claude Code statusline for Node.js
6
+ * Cross-platform support: Windows, macOS, Linux
7
+ * Theme: detailed | Colors: true | Features: directory, git, model, usage, session, tokens
8
+ * No external dependencies - uses only Node.js built-in modules
9
+ */
10
+
11
+ const { stdin, stdout, env } = require('process');
12
+ const { execSync } = require('child_process');
13
+ const os = require('os');
14
+
15
+ // Configuration
16
+ const USE_COLOR = !env.NO_COLOR && stdout.isTTY;
17
+
18
+ // Color helpers
19
+ const color = (code) => USE_COLOR ? `\x1b[${code}m` : '';
20
+ const reset = () => USE_COLOR ? '\x1b[0m' : '';
21
+
22
+ // Color definitions
23
+ const DirColor = color('1;36'); // cyan
24
+ const GitColor = color('1;32'); // green
25
+ const ModelColor = color('1;35'); // magenta
26
+ const VersionColor = color('1;33'); // yellow
27
+ const UsageColor = color('1;35'); // magenta
28
+ const CostColor = color('1;36'); // cyan
29
+ const Reset = reset();
30
+
31
+ /**
32
+ * Safe command execution wrapper
33
+ */
34
+ function exec(cmd) {
35
+ try {
36
+ return execSync(cmd, {
37
+ encoding: 'utf8',
38
+ stdio: ['pipe', 'pipe', 'ignore'],
39
+ windowsHide: true
40
+ }).trim();
41
+ } catch (err) {
42
+ return '';
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Convert ISO8601 timestamp to Unix epoch
48
+ */
49
+ function toEpoch(timestamp) {
50
+ try {
51
+ const date = new Date(timestamp);
52
+ return Math.floor(date.getTime() / 1000);
53
+ } catch (err) {
54
+ return 0;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Format epoch timestamp as HH:mm
60
+ */
61
+ function formatTimeHM(epoch) {
62
+ try {
63
+ const date = new Date(epoch * 1000);
64
+ const hours = date.getHours().toString().padStart(2, '0');
65
+ const minutes = date.getMinutes().toString().padStart(2, '0');
66
+ return `${hours}:${minutes}`;
67
+ } catch (err) {
68
+ return '00:00';
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Get session color based on remaining percentage
74
+ */
75
+ function getSessionColor(sessionPercent) {
76
+ if (!USE_COLOR) return '';
77
+
78
+ const remaining = 100 - sessionPercent;
79
+ if (remaining <= 10) {
80
+ return '\x1b[1;31m'; // red
81
+ } else if (remaining <= 25) {
82
+ return '\x1b[1;33m'; // yellow
83
+ } else {
84
+ return '\x1b[1;32m'; // green
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Expand home directory to ~
90
+ */
91
+ function expandHome(path) {
92
+ const homeDir = os.homedir();
93
+ if (path.startsWith(homeDir)) {
94
+ return path.replace(homeDir, '~');
95
+ }
96
+ return path;
97
+ }
98
+
99
+ /**
100
+ * Read stdin asynchronously
101
+ */
102
+ async function readStdin() {
103
+ return new Promise((resolve, reject) => {
104
+ const chunks = [];
105
+ stdin.setEncoding('utf8');
106
+
107
+ stdin.on('data', (chunk) => {
108
+ chunks.push(chunk);
109
+ });
110
+
111
+ stdin.on('end', () => {
112
+ resolve(chunks.join(''));
113
+ });
114
+
115
+ stdin.on('error', (err) => {
116
+ reject(err);
117
+ });
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Main function
123
+ */
124
+ async function main() {
125
+ try {
126
+ // Read and parse JSON input
127
+ const input = await readStdin();
128
+ if (!input.trim()) {
129
+ console.error('No input provided');
130
+ process.exit(1);
131
+ }
132
+
133
+ const data = JSON.parse(input);
134
+
135
+ // Extract basic information
136
+ let currentDir = 'unknown';
137
+ if (data.workspace?.current_dir) {
138
+ currentDir = data.workspace.current_dir;
139
+ } else if (data.cwd) {
140
+ currentDir = data.cwd;
141
+ }
142
+ currentDir = expandHome(currentDir);
143
+
144
+ const modelName = data.model?.display_name || 'Claude';
145
+ const modelVersion = data.model?.version && data.model.version !== 'null' ? data.model.version : '';
146
+
147
+ // Git branch detection
148
+ let gitBranch = '';
149
+ const gitCheck = exec('git rev-parse --git-dir');
150
+ if (gitCheck) {
151
+ gitBranch = exec('git branch --show-current');
152
+ if (!gitBranch) {
153
+ gitBranch = exec('git rev-parse --short HEAD');
154
+ }
155
+ }
156
+
157
+ // ccusage integration
158
+ let sessionText = '';
159
+ let sessionPercent = 0;
160
+ let costUSD = '';
161
+ let costPerHour = '';
162
+ let totalTokens = '';
163
+
164
+ try {
165
+ // Try npx first, then ccusage
166
+ let blocksOutput = exec('npx ccusage@latest blocks --json');
167
+ if (!blocksOutput) {
168
+ blocksOutput = exec('ccusage blocks --json');
169
+ }
170
+
171
+ if (blocksOutput) {
172
+ const blocks = JSON.parse(blocksOutput);
173
+ const activeBlock = blocks.blocks?.find(b => b.isActive === true);
174
+
175
+ if (activeBlock) {
176
+ costUSD = activeBlock.costUSD || '';
177
+ costPerHour = activeBlock.burnRate?.costPerHour || '';
178
+ totalTokens = activeBlock.totalTokens || '';
179
+
180
+ // Session time calculation
181
+ const resetTimeStr = activeBlock.usageLimitResetTime || activeBlock.endTime;
182
+ const startTimeStr = activeBlock.startTime;
183
+
184
+ if (resetTimeStr && startTimeStr) {
185
+ const startSec = toEpoch(startTimeStr);
186
+ const endSec = toEpoch(resetTimeStr);
187
+ const nowSec = Math.floor(Date.now() / 1000);
188
+
189
+ let total = endSec - startSec;
190
+ if (total < 1) total = 1;
191
+
192
+ let elapsed = nowSec - startSec;
193
+ if (elapsed < 0) elapsed = 0;
194
+ if (elapsed > total) elapsed = total;
195
+
196
+ sessionPercent = Math.floor(elapsed * 100 / total);
197
+ let remaining = endSec - nowSec;
198
+ if (remaining < 0) remaining = 0;
199
+
200
+ const rh = Math.floor(remaining / 3600);
201
+ const rm = Math.floor((remaining % 3600) / 60);
202
+ const endHM = formatTimeHM(endSec);
203
+
204
+ sessionText = `${rh}h ${rm}m until reset at ${endHM}`;
205
+ }
206
+ }
207
+ }
208
+ } catch (err) {
209
+ // Silent fail - ccusage not available or error
210
+ }
211
+
212
+ // Render statusline
213
+ let output = '';
214
+
215
+ // Directory
216
+ output += `📁 ${DirColor}${currentDir}${Reset}`;
217
+
218
+ // Git branch
219
+ if (gitBranch) {
220
+ output += ` 🌿 ${GitColor}${gitBranch}${Reset}`;
221
+ }
222
+
223
+ // Model
224
+ output += ` 🤖 ${ModelColor}${modelName}${Reset}`;
225
+
226
+ // Model version
227
+ if (modelVersion) {
228
+ output += ` 🏷️ ${VersionColor}${modelVersion}${Reset}`;
229
+ }
230
+
231
+ // Session time
232
+ if (sessionText) {
233
+ const sessionColorCode = getSessionColor(sessionPercent);
234
+ output += ` ⌛ ${sessionColorCode}${sessionText}${Reset}`;
235
+ }
236
+
237
+ // Cost
238
+ if (costUSD && /^\d+(\.\d+)?$/.test(costUSD)) {
239
+ const costUSDNum = parseFloat(costUSD);
240
+ if (costPerHour && /^\d+(\.\d+)?$/.test(costPerHour)) {
241
+ const costPerHourNum = parseFloat(costPerHour);
242
+ output += ` 💵 ${CostColor}$${costUSDNum.toFixed(2)} ($${costPerHourNum.toFixed(2)}/h)${Reset}`;
243
+ } else {
244
+ output += ` 💵 ${CostColor}$${costUSDNum.toFixed(2)}${Reset}`;
245
+ }
246
+ }
247
+
248
+ // Tokens
249
+ if (totalTokens && /^\d+$/.test(totalTokens.toString())) {
250
+ output += ` 📊 ${UsageColor}${totalTokens} tok${Reset}`;
251
+ }
252
+
253
+ console.log(output);
254
+ } catch (err) {
255
+ console.error('Error:', err.message);
256
+ process.exit(1);
257
+ }
258
+ }
259
+
260
+ main().catch(err => {
261
+ console.error('Fatal error:', err);
262
+ process.exit(1);
263
+ });
@@ -0,0 +1,312 @@
1
+ #Requires -Version 5.1
2
+ # Custom Claude Code statusline for PowerShell
3
+ # Cross-platform support: Windows PowerShell 5.1+, PowerShell Core 7+
4
+ # Theme: detailed | Colors: true | Features: directory, git, model, usage, session, tokens
5
+
6
+ # Set UTF-8 encoding
7
+ [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
8
+ $OutputEncoding = [System.Text.Encoding]::UTF8
9
+
10
+ # Enable virtual terminal sequences for ANSI colors
11
+ function Enable-VirtualTerminal {
12
+ if ($PSVersionTable.PSVersion.Major -ge 6) {
13
+ # PowerShell Core 7+ has built-in support
14
+ return
15
+ }
16
+
17
+ # Windows PowerShell 5.1 - enable virtual terminal
18
+ try {
19
+ $null = [System.Console]::OutputEncoding
20
+ if ($PSVersionTable.PSVersion.Major -eq 5) {
21
+ # Attempt to enable virtual terminal processing
22
+ $signature = @'
23
+ [DllImport("kernel32.dll", SetLastError = true)]
24
+ public static extern IntPtr GetStdHandle(int nStdHandle);
25
+
26
+ [DllImport("kernel32.dll", SetLastError = true)]
27
+ public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
28
+
29
+ [DllImport("kernel32.dll", SetLastError = true)]
30
+ public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
31
+ '@
32
+ $type = Add-Type -MemberDefinition $signature -Name 'WinAPI' -Namespace 'VirtualTerminal' -PassThru -ErrorAction SilentlyContinue
33
+ if ($type) {
34
+ $handle = $type::GetStdHandle(-11) # STD_OUTPUT_HANDLE
35
+ $mode = 0
36
+ $null = $type::GetConsoleMode($handle, [ref]$mode)
37
+ $mode = $mode -bor 0x0004 # ENABLE_VIRTUAL_TERMINAL_PROCESSING
38
+ $null = $type::SetConsoleMode($handle, $mode)
39
+ }
40
+ }
41
+ }
42
+ catch {
43
+ # Silent fail - colors just won't work
44
+ }
45
+ }
46
+
47
+ # Initialize color support
48
+ $script:UseColor = $true
49
+ if ($env:NO_COLOR) {
50
+ $script:UseColor = $false
51
+ }
52
+ elseif (-not [Console]::IsOutputRedirected -and [Console]::OutputEncoding) {
53
+ Enable-VirtualTerminal
54
+ }
55
+ else {
56
+ $script:UseColor = $false
57
+ }
58
+
59
+ # Color helper functions
60
+ function Get-Color {
61
+ param([string]$Code)
62
+ if ($script:UseColor) { return "`e[${Code}m" }
63
+ return ""
64
+ }
65
+
66
+ function Get-Reset {
67
+ if ($script:UseColor) { return "`e[0m" }
68
+ return ""
69
+ }
70
+
71
+ # Color definitions
72
+ $DirColor = Get-Color "1;36" # cyan
73
+ $GitColor = Get-Color "1;32" # green
74
+ $ModelColor = Get-Color "1;35" # magenta
75
+ $VersionColor = Get-Color "1;33" # yellow
76
+ $UsageColor = Get-Color "1;35" # magenta
77
+ $CostColor = Get-Color "1;36" # cyan
78
+ $Reset = Get-Reset
79
+
80
+ # Time conversion functions
81
+ function ConvertTo-Epoch {
82
+ param([string]$Timestamp)
83
+
84
+ try {
85
+ # Parse ISO8601 timestamp
86
+ $dt = [DateTime]::Parse($Timestamp).ToUniversalTime()
87
+ $epoch = [DateTimeOffset]::new($dt).ToUnixTimeSeconds()
88
+ return $epoch
89
+ }
90
+ catch {
91
+ return 0
92
+ }
93
+ }
94
+
95
+ function Format-TimeHM {
96
+ param([long]$Epoch)
97
+
98
+ try {
99
+ $dt = [DateTimeOffset]::FromUnixTimeSeconds($Epoch).LocalDateTime
100
+ return $dt.ToString("HH:mm")
101
+ }
102
+ catch {
103
+ return "00:00"
104
+ }
105
+ }
106
+
107
+ function Get-ProgressBar {
108
+ param(
109
+ [int]$Percent = 0,
110
+ [int]$Width = 10
111
+ )
112
+
113
+ if ($Percent -lt 0) { $Percent = 0 }
114
+ if ($Percent -gt 100) { $Percent = 100 }
115
+
116
+ $filled = [Math]::Floor($Percent * $Width / 100)
117
+ $empty = $Width - $filled
118
+
119
+ $bar = ("=" * $filled) + ("-" * $empty)
120
+ return $bar
121
+ }
122
+
123
+ function Get-SessionColor {
124
+ param([int]$SessionPercent)
125
+
126
+ if (-not $script:UseColor) { return "" }
127
+
128
+ $remaining = 100 - $SessionPercent
129
+ if ($remaining -le 10) {
130
+ return "`e[1;31m" # red
131
+ }
132
+ elseif ($remaining -le 25) {
133
+ return "`e[1;33m" # yellow
134
+ }
135
+ else {
136
+ return "`e[1;32m" # green
137
+ }
138
+ }
139
+
140
+ # Read JSON from stdin
141
+ try {
142
+ $inputLines = @()
143
+ while ($null -ne ($line = [Console]::In.ReadLine())) {
144
+ $inputLines += $line
145
+ }
146
+ $inputJson = $inputLines -join "`n"
147
+
148
+ if ([string]::IsNullOrWhiteSpace($inputJson)) {
149
+ Write-Error "No input provided"
150
+ exit 1
151
+ }
152
+
153
+ $data = $inputJson | ConvertFrom-Json
154
+ }
155
+ catch {
156
+ Write-Error "Failed to parse JSON input: $_"
157
+ exit 1
158
+ }
159
+
160
+ # Extract basic information
161
+ $currentDir = "unknown"
162
+ if ($data.workspace.current_dir) {
163
+ $currentDir = $data.workspace.current_dir
164
+ }
165
+ elseif ($data.cwd) {
166
+ $currentDir = $data.cwd
167
+ }
168
+
169
+ # Replace home directory with ~
170
+ $homeDir = $env:USERPROFILE
171
+ if (-not $homeDir) {
172
+ $homeDir = $env:HOME
173
+ }
174
+ if ($homeDir -and $currentDir.StartsWith($homeDir)) {
175
+ $currentDir = $currentDir.Replace($homeDir, "~")
176
+ }
177
+
178
+ $modelName = "Claude"
179
+ if ($data.model.display_name) {
180
+ $modelName = $data.model.display_name
181
+ }
182
+
183
+ $modelVersion = ""
184
+ if ($data.model.version -and $data.model.version -ne "null") {
185
+ $modelVersion = $data.model.version
186
+ }
187
+
188
+ # Git branch detection
189
+ $gitBranch = ""
190
+ try {
191
+ $null = git rev-parse --git-dir 2>$null
192
+ if ($LASTEXITCODE -eq 0) {
193
+ $gitBranch = git branch --show-current 2>$null
194
+ if ([string]::IsNullOrWhiteSpace($gitBranch)) {
195
+ $gitBranch = git rev-parse --short HEAD 2>$null
196
+ }
197
+ }
198
+ }
199
+ catch {
200
+ # Not in a git repository
201
+ }
202
+
203
+ # ccusage integration
204
+ $sessionText = ""
205
+ $sessionPercent = 0
206
+ $sessionBar = ""
207
+ $costUSD = ""
208
+ $costPerHour = ""
209
+ $totalTokens = ""
210
+
211
+ try {
212
+ # Try npx first, then ccusage
213
+ $blocksOutput = $null
214
+ try {
215
+ $blocksOutput = npx ccusage@latest blocks --json 2>$null
216
+ }
217
+ catch {
218
+ try {
219
+ $blocksOutput = ccusage blocks --json 2>$null
220
+ }
221
+ catch {
222
+ # ccusage not available
223
+ }
224
+ }
225
+
226
+ if ($blocksOutput) {
227
+ $blocks = $blocksOutput | ConvertFrom-Json
228
+ $activeBlock = $blocks.blocks | Where-Object { $_.isActive -eq $true } | Select-Object -First 1
229
+
230
+ if ($activeBlock) {
231
+ if ($activeBlock.costUSD) {
232
+ $costUSD = $activeBlock.costUSD
233
+ }
234
+ if ($activeBlock.burnRate.costPerHour) {
235
+ $costPerHour = $activeBlock.burnRate.costPerHour
236
+ }
237
+ if ($activeBlock.totalTokens) {
238
+ $totalTokens = $activeBlock.totalTokens
239
+ }
240
+
241
+ # Session time calculation
242
+ $resetTimeStr = $activeBlock.usageLimitResetTime
243
+ if (-not $resetTimeStr) {
244
+ $resetTimeStr = $activeBlock.endTime
245
+ }
246
+ $startTimeStr = $activeBlock.startTime
247
+
248
+ if ($resetTimeStr -and $startTimeStr) {
249
+ $startSec = ConvertTo-Epoch $startTimeStr
250
+ $endSec = ConvertTo-Epoch $resetTimeStr
251
+ $nowSec = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
252
+
253
+ $total = $endSec - $startSec
254
+ if ($total -lt 1) { $total = 1 }
255
+
256
+ $elapsed = $nowSec - $startSec
257
+ if ($elapsed -lt 0) { $elapsed = 0 }
258
+ if ($elapsed -gt $total) { $elapsed = $total }
259
+
260
+ $sessionPercent = [Math]::Floor($elapsed * 100 / $total)
261
+ $remaining = $endSec - $nowSec
262
+ if ($remaining -lt 0) { $remaining = 0 }
263
+
264
+ $rh = [Math]::Floor($remaining / 3600)
265
+ $rm = [Math]::Floor(($remaining % 3600) / 60)
266
+ $endHM = Format-TimeHM $endSec
267
+
268
+ $sessionText = "${rh}h ${rm}m until reset at ${endHM}"
269
+ }
270
+ }
271
+ }
272
+ # Directory
273
+ $output += "📁 ${DirColor}${currentDir}${Reset}"
274
+
275
+ # Git branch
276
+ if ($gitBranch) {
277
+ $output += " 🌿 ${GitColor}${gitBranch}${Reset}"
278
+ }
279
+
280
+ # Model
281
+ $output += " 🤖 ${ModelColor}${modelName}${Reset}"
282
+
283
+ # Model version
284
+ if ($modelVersion) {
285
+ $output += " 🏷️ ${VersionColor}${modelVersion}${Reset}"
286
+ }
287
+
288
+ # Session time
289
+ if ($sessionText) {
290
+ $sessionColorCode = Get-SessionColor $sessionPercent
291
+ $output += " ⌛ ${sessionColorCode}${sessionText}${Reset}"
292
+ }
293
+
294
+ # Cost
295
+ if ($costUSD -and $costUSD -match '^\d+(\.\d+)?$') {
296
+ if ($costPerHour -and $costPerHour -match '^\d+(\.\d+)?$') {
297
+ $costUSDFormatted = [string]::Format("{0:F2}", [double]$costUSD)
298
+ $costPerHourFormatted = [string]::Format("{0:F2}", [double]$costPerHour)
299
+ $output += " 💵 ${CostColor}`$$costUSDFormatted (`$$costPerHourFormatted/h)${Reset}"
300
+ }
301
+ else {
302
+ $costUSDFormatted = [string]::Format("{0:F2}", [double]$costUSD)
303
+ $output += " 💵 ${CostColor}`$$costUSDFormatted${Reset}"
304
+ }
305
+ }
306
+
307
+ # Tokens
308
+ if ($totalTokens -and $totalTokens -match '^\d+$') {
309
+ $output += " 📊 ${UsageColor}${totalTokens} tok${Reset}"
310
+ }
311
+
312
+ Write-Host $output