claude-skill-lord 2.1.0 → 2.1.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/CLAUDE.md +3 -3
- package/README.md +1 -1
- package/hooks/scripts/check-console-log.js +1 -1
- package/hooks/scripts/discord-notify.sh +10 -13
- package/hooks/scripts/post-edit-format.js +1 -1
- package/hooks/scripts/quality-gate.js +1 -1
- package/hooks/scripts/run-with-flags.js +1 -1
- package/hooks/scripts/session-end.js +1 -1
- package/hooks/scripts/telegram-notify.sh +7 -2
- package/hooks/telegram-hook-setup.md +17 -17
- package/manifests/install-modules.json +3 -3
- package/package.json +4 -4
- package/scripts/install.js +7 -1
- package/scripts/lib/package-manager.js +83 -0
- package/scripts/lib/utils.js +1 -154
- package/scripts/prepare-release-assets.cjs +81 -0
- package/skills/ai-multimodal/scripts/tests/test_failures.log +258 -0
- package/skills/manifest.json +10 -10
- package/hooks/scripts/check-hook-enabled.js +0 -12
- package/hooks/scripts/discord_notify.sh +0 -221
- package/hooks/scripts/post-edit-console-warn.js +0 -54
- package/hooks/scripts/run-with-flags-shell.sh +0 -32
- package/hooks/scripts/scout-block.ps1 +0 -65
- package/hooks/scripts/scout-block.sh +0 -51
- package/hooks/scripts/send-discord.sh +0 -75
- package/hooks/scripts/session-start.js +0 -98
- package/hooks/scripts/telegram_notify.sh +0 -158
- package/scripts/lib/resolve-root.js +0 -89
- /package/skills/claude-code/{SKILL.md → skill.md} +0 -0
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
{
|
|
10
10
|
"name": "claude-skill-lord",
|
|
11
11
|
"source": "./",
|
|
12
|
-
"description": "Curated Claude Code plugin —
|
|
13
|
-
"version": "1.
|
|
12
|
+
"description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
|
|
13
|
+
"version": "2.1.0",
|
|
14
14
|
"category": "workflow",
|
|
15
15
|
"tags": ["agents", "skills", "commands", "hooks", "skill-routing", "quality-gate", "tdd", "code-review"],
|
|
16
16
|
"strict": false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-skill-lord",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Curated Claude Code plugin — 43 agents,
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Dong Anh",
|
|
7
7
|
"email": "donganhvu20@gmail.com"
|
package/CLAUDE.md
CHANGED
|
@@ -11,7 +11,7 @@ Curated Claude Code plugin with intelligent skill routing, structured agents, an
|
|
|
11
11
|
|
|
12
12
|
**IMPORTANT:** Follow development rules strictly. Research & Reuse is mandatory FIRST step before implementation.
|
|
13
13
|
|
|
14
|
-
## Agents (
|
|
14
|
+
## Agents (43)
|
|
15
15
|
|
|
16
16
|
### Core Agents
|
|
17
17
|
| Agent | Role |
|
|
@@ -72,7 +72,7 @@ Curated Claude Code plugin with intelligent skill routing, structured agents, an
|
|
|
72
72
|
|
|
73
73
|
All skills live in `./skills/<name>/SKILL.md` — flat structure, no tiers.
|
|
74
74
|
|
|
75
|
-
## Commands (
|
|
75
|
+
## Commands (114)
|
|
76
76
|
|
|
77
77
|
### Core Commands
|
|
78
78
|
| Command | Description |
|
|
@@ -85,7 +85,7 @@ All skills live in `./skills/<name>/SKILL.md` — flat structure, no tiers.
|
|
|
85
85
|
| /tdd | Test-driven development workflow |
|
|
86
86
|
| /debug | Deep issue analysis |
|
|
87
87
|
| /code-review | Code review |
|
|
88
|
-
| /route | Get skill recommendations for your task |
|
|
88
|
+
| /model-route | Get skill recommendations for your task |
|
|
89
89
|
| /audit | Run quality checks |
|
|
90
90
|
| /scout | Search codebase (variants: ext) |
|
|
91
91
|
| /bootstrap | Initialize new projects (variants: auto, auto/fast) |
|
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ The most common workflow — plan first, then implement, then validate:
|
|
|
139
139
|
|
|
140
140
|
```
|
|
141
141
|
/scout src/ find all API endpoints
|
|
142
|
-
/route I need to refactor the authentication module
|
|
142
|
+
/model-route I need to refactor the authentication module
|
|
143
143
|
/review:codebase
|
|
144
144
|
```
|
|
145
145
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const fs = require('fs');
|
|
17
|
-
const { isGitRepo, getGitModifiedFiles, readFile, log } = require('
|
|
17
|
+
const { isGitRepo, getGitModifiedFiles, readFile, log } = require('../../scripts/lib/utils');
|
|
18
18
|
|
|
19
19
|
// Files where console.log is expected and should not trigger warnings
|
|
20
20
|
const EXCLUDED_PATTERNS = [
|
|
@@ -27,19 +27,16 @@ fi
|
|
|
27
27
|
|
|
28
28
|
send_discord_embed() {
|
|
29
29
|
local title="$1" description="$2" color="$3" fields="$4"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"color"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"fields"
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
EOF
|
|
42
|
-
)
|
|
30
|
+
# Use jq to safely construct JSON (prevents injection from unescaped variables)
|
|
31
|
+
local payload
|
|
32
|
+
payload=$(jq -n \
|
|
33
|
+
--arg title "$title" \
|
|
34
|
+
--arg desc "$description" \
|
|
35
|
+
--argjson color "$color" \
|
|
36
|
+
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \
|
|
37
|
+
--arg footer "CSL • ${PROJECT_NAME}" \
|
|
38
|
+
--argjson fields "$fields" \
|
|
39
|
+
'{embeds: [{title: $title, description: $desc, color: $color, timestamp: $ts, footer: {text: $footer}, fields: $fields}]}')
|
|
43
40
|
curl -s -X POST "$DISCORD_WEBHOOK_URL" -H "Content-Type: application/json" -d "$payload" > /dev/null 2>&1
|
|
44
41
|
}
|
|
45
42
|
|
|
@@ -23,7 +23,7 @@ const path = require('path');
|
|
|
23
23
|
// Shell metacharacters that cmd.exe interprets as command separators/operators
|
|
24
24
|
const UNSAFE_PATH_CHARS = /[&|<>^%!]/;
|
|
25
25
|
|
|
26
|
-
const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('
|
|
26
|
+
const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../../scripts/lib/resolve-formatter');
|
|
27
27
|
|
|
28
28
|
const MAX_STDIN = 1024 * 1024; // 1MB limit
|
|
29
29
|
|
|
@@ -18,7 +18,7 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const { spawnSync } = require('child_process');
|
|
20
20
|
|
|
21
|
-
const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('
|
|
21
|
+
const { findProjectRoot, detectFormatter, resolveFormatterBin } = require('../../scripts/lib/resolve-formatter');
|
|
22
22
|
|
|
23
23
|
const MAX_STDIN = 1024 * 1024;
|
|
24
24
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const { spawnSync } = require('child_process');
|
|
14
|
-
const { isHookEnabled } = require('
|
|
14
|
+
const { isHookEnabled } = require('../../scripts/lib/hook-flags');
|
|
15
15
|
|
|
16
16
|
const MAX_STDIN = 1024 * 1024;
|
|
17
17
|
|
|
@@ -27,10 +27,15 @@ fi
|
|
|
27
27
|
|
|
28
28
|
send_telegram() {
|
|
29
29
|
local message="$1"
|
|
30
|
-
|
|
30
|
+
# Use jq to safely construct JSON payload (prevents injection from unescaped variables)
|
|
31
|
+
local payload
|
|
32
|
+
payload=$(jq -n \
|
|
33
|
+
--arg chat_id "$TELEGRAM_CHAT_ID" \
|
|
34
|
+
--arg text "$message" \
|
|
35
|
+
'{chat_id: $chat_id, text: $text, parse_mode: "Markdown", disable_web_page_preview: true}')
|
|
31
36
|
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
|
32
37
|
-H "Content-Type: application/json" \
|
|
33
|
-
-d "
|
|
38
|
+
-d "$payload" > /dev/null
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
case "$HOOK_TYPE" in
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
The Telegram hook (`
|
|
5
|
+
The Telegram hook (`telegram-notify.sh`) automatically sends notifications when Claude Code sessions stop or subagents complete tasks. It provides detailed summaries including tool usage, files modified, and operation counts.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -181,13 +181,13 @@ Hooks are configured in `.claude/settings.local.json`:
|
|
|
181
181
|
"Stop": [{
|
|
182
182
|
"hooks": [{
|
|
183
183
|
"type": "command",
|
|
184
|
-
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/
|
|
184
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram-notify.sh"
|
|
185
185
|
}]
|
|
186
186
|
}],
|
|
187
187
|
"SubagentStop": [{
|
|
188
188
|
"hooks": [{
|
|
189
189
|
"type": "command",
|
|
190
|
-
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/
|
|
190
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/telegram-notify.sh"
|
|
191
191
|
}]
|
|
192
192
|
}]
|
|
193
193
|
}
|
|
@@ -203,7 +203,7 @@ Hooks are configured in `.claude/settings.local.json`:
|
|
|
203
203
|
### 5. Make Script Executable
|
|
204
204
|
|
|
205
205
|
```bash
|
|
206
|
-
chmod +x .claude/hooks/
|
|
206
|
+
chmod +x .claude/hooks/telegram-notify.sh
|
|
207
207
|
```
|
|
208
208
|
|
|
209
209
|
### 6. Verify Setup
|
|
@@ -220,7 +220,7 @@ echo '{
|
|
|
220
220
|
{"tool": "Edit", "parameters": {"file_path": "test.ts"}},
|
|
221
221
|
{"tool": "Bash", "parameters": {"command": "npm test"}}
|
|
222
222
|
]
|
|
223
|
-
}' | ./.claude/hooks/
|
|
223
|
+
}' | ./.claude/hooks/telegram-notify.sh
|
|
224
224
|
```
|
|
225
225
|
|
|
226
226
|
**Expected output:**
|
|
@@ -492,13 +492,13 @@ jq --version
|
|
|
492
492
|
|
|
493
493
|
3. **Verify script is executable:**
|
|
494
494
|
```bash
|
|
495
|
-
ls -l .claude/hooks/
|
|
495
|
+
ls -l .claude/hooks/telegram-notify.sh
|
|
496
496
|
# Should show: -rwxr-xr-x
|
|
497
497
|
```
|
|
498
498
|
|
|
499
499
|
4. **Make script executable if needed:**
|
|
500
500
|
```bash
|
|
501
|
-
chmod +x .claude/hooks/
|
|
501
|
+
chmod +x .claude/hooks/telegram-notify.sh
|
|
502
502
|
```
|
|
503
503
|
|
|
504
504
|
5. **Test hook manually (see "Verify Setup" section)**
|
|
@@ -519,7 +519,7 @@ jq --version
|
|
|
519
519
|
- Script uses `"parse_mode": "Markdown"`
|
|
520
520
|
|
|
521
521
|
2. **Check message escaping in script:**
|
|
522
|
-
- Edit `
|
|
522
|
+
- Edit `telegram-notify.sh`
|
|
523
523
|
- Look for line: `local escaped_message=$(echo "$message" | jq -Rs .)`
|
|
524
524
|
- This should properly escape for JSON
|
|
525
525
|
|
|
@@ -536,12 +536,12 @@ jq --version
|
|
|
536
536
|
|
|
537
537
|
**Solution:**
|
|
538
538
|
```bash
|
|
539
|
-
chmod +x .claude/hooks/
|
|
539
|
+
chmod +x .claude/hooks/telegram-notify.sh
|
|
540
540
|
```
|
|
541
541
|
|
|
542
542
|
**Verify:**
|
|
543
543
|
```bash
|
|
544
|
-
ls -l .claude/hooks/
|
|
544
|
+
ls -l .claude/hooks/telegram-notify.sh
|
|
545
545
|
# Output should show: -rwxr-xr-x
|
|
546
546
|
```
|
|
547
547
|
|
|
@@ -561,7 +561,7 @@ TELEGRAM_CHAT_ID_ERROR=987654321 # Error notifications
|
|
|
561
561
|
|
|
562
562
|
**Modified script logic:**
|
|
563
563
|
```bash
|
|
564
|
-
# In
|
|
564
|
+
# In telegram-notify.sh, add conditional chat ID selection
|
|
565
565
|
if [[ "$HOOK_TYPE" == "Stop" ]] && [[ $TOTAL_TOOLS -gt 20 ]]; then
|
|
566
566
|
# Large operations go to success channel
|
|
567
567
|
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID_SUCCESS:-$TELEGRAM_CHAT_ID}"
|
|
@@ -572,7 +572,7 @@ fi
|
|
|
572
572
|
|
|
573
573
|
Only send notifications for significant events:
|
|
574
574
|
|
|
575
|
-
**Edit `
|
|
575
|
+
**Edit `telegram-notify.sh`:**
|
|
576
576
|
```bash
|
|
577
577
|
# After line 65 (TOTAL_TOOLS calculation), add:
|
|
578
578
|
|
|
@@ -604,7 +604,7 @@ fi
|
|
|
604
604
|
|
|
605
605
|
### Custom Message Formatting
|
|
606
606
|
|
|
607
|
-
Modify notification format in `
|
|
607
|
+
Modify notification format in `telegram-notify.sh`:
|
|
608
608
|
|
|
609
609
|
**Add Git branch info:**
|
|
610
610
|
```bash
|
|
@@ -650,7 +650,7 @@ Prevent notification spam:
|
|
|
650
650
|
|
|
651
651
|
**Create rate limit file:**
|
|
652
652
|
```bash
|
|
653
|
-
# Add to
|
|
653
|
+
# Add to telegram-notify.sh, after line 55:
|
|
654
654
|
|
|
655
655
|
RATE_LIMIT_FILE="/tmp/telegram_notify_last_sent"
|
|
656
656
|
RATE_LIMIT_SECONDS=60
|
|
@@ -690,7 +690,7 @@ echo '{
|
|
|
690
690
|
{"tool": "Bash", "parameters": {"command": "npm test"}},
|
|
691
691
|
{"tool": "TodoWrite", "parameters": {}}
|
|
692
692
|
]
|
|
693
|
-
}' | ./.claude/hooks/
|
|
693
|
+
}' | ./.claude/hooks/telegram-notify.sh
|
|
694
694
|
```
|
|
695
695
|
|
|
696
696
|
**SubagentStop event:**
|
|
@@ -700,7 +700,7 @@ echo '{
|
|
|
700
700
|
"projectDir": "'"$(pwd)"'",
|
|
701
701
|
"sessionId": "test-456",
|
|
702
702
|
"subagentType": "planner"
|
|
703
|
-
}' | ./.claude/hooks/
|
|
703
|
+
}' | ./.claude/hooks/telegram-notify.sh
|
|
704
704
|
```
|
|
705
705
|
|
|
706
706
|
## Security Best Practices
|
|
@@ -748,7 +748,7 @@ echo '{
|
|
|
748
748
|
|
|
749
749
|
## Reference
|
|
750
750
|
|
|
751
|
-
**Script Location:** `.claude/hooks/
|
|
751
|
+
**Script Location:** `.claude/hooks/telegram-notify.sh`
|
|
752
752
|
|
|
753
753
|
**Configuration:** `.claude/config.json`
|
|
754
754
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"id": "skills",
|
|
24
24
|
"kind": "skills",
|
|
25
|
-
"description": "All
|
|
25
|
+
"description": "All 161 skills — debugging, testing, frontend/backend, databases, security, UI/UX, patterns, languages, frameworks, and more",
|
|
26
26
|
"paths": ["skills/"],
|
|
27
27
|
"defaultInstall": true,
|
|
28
28
|
"cost": "medium",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
{
|
|
32
32
|
"id": "rules",
|
|
33
33
|
"kind": "rules",
|
|
34
|
-
"description": "Language-specific coding rules —
|
|
34
|
+
"description": "Language-specific coding rules — 11 languages (TypeScript, Python, Go, Rust, Java, Kotlin, C++, C#, PHP, Perl, Swift) + common",
|
|
35
35
|
"paths": ["rules/"],
|
|
36
36
|
"defaultInstall": true,
|
|
37
37
|
"cost": "light",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"id": "hooks-essential",
|
|
70
70
|
"kind": "hooks",
|
|
71
71
|
"description": "Essential hooks — security, formatting, type checking, notifications",
|
|
72
|
-
"paths": ["hooks/"],
|
|
72
|
+
"paths": ["hooks/", "scripts/lib/"],
|
|
73
73
|
"defaultInstall": true,
|
|
74
74
|
"cost": "light",
|
|
75
75
|
"stability": "stable"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-skill-lord",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Curated Claude Code plugin — 43 agents, 161 skills, 114 commands, 11 language rules",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Dong Anh",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/donganhvuphp/Claude-Skills-Lord.git"
|
|
12
|
+
"url": "git+https://github.com/donganhvuphp/Claude-Skills-Lord.git"
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://github.com/donganhvuphp/Claude-Skills-Lord",
|
|
15
15
|
"bugs": {
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"code-review"
|
|
28
28
|
],
|
|
29
29
|
"bin": {
|
|
30
|
-
"csl": "
|
|
31
|
-
"skill-lord": "
|
|
30
|
+
"csl": "scripts/sl.js",
|
|
31
|
+
"skill-lord": "scripts/sl.js"
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
".claude-plugin/",
|
package/scripts/install.js
CHANGED
|
@@ -54,7 +54,13 @@ if (!fs.existsSync(modulesPath)) {
|
|
|
54
54
|
process.exit(1);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
let modules;
|
|
58
|
+
try {
|
|
59
|
+
modules = JSON.parse(fs.readFileSync(modulesPath, 'utf8'));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
console.error(`Error: Failed to parse install-modules.json: ${err.message}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
58
64
|
|
|
59
65
|
// Install all modules (optionally skip fonts)
|
|
60
66
|
let allModules = modules.modules;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Package Manager Detection
|
|
5
|
+
*
|
|
6
|
+
* Detects which package manager (npm, yarn, pnpm, bun) the project uses
|
|
7
|
+
* by checking lock files and environment variables.
|
|
8
|
+
*
|
|
9
|
+
* Used by: resolve-formatter.js, session hooks
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Package manager configurations
|
|
19
|
+
*/
|
|
20
|
+
const PM_CONFIGS = {
|
|
21
|
+
bun: {
|
|
22
|
+
name: 'bun',
|
|
23
|
+
lockFile: 'bun.lockb',
|
|
24
|
+
execCmd: 'bunx',
|
|
25
|
+
installCmd: 'bun install',
|
|
26
|
+
runCmd: 'bun run',
|
|
27
|
+
},
|
|
28
|
+
pnpm: {
|
|
29
|
+
name: 'pnpm',
|
|
30
|
+
lockFile: 'pnpm-lock.yaml',
|
|
31
|
+
execCmd: 'pnpm exec',
|
|
32
|
+
installCmd: 'pnpm install',
|
|
33
|
+
runCmd: 'pnpm run',
|
|
34
|
+
},
|
|
35
|
+
yarn: {
|
|
36
|
+
name: 'yarn',
|
|
37
|
+
lockFile: 'yarn.lock',
|
|
38
|
+
execCmd: 'yarn dlx',
|
|
39
|
+
installCmd: 'yarn install',
|
|
40
|
+
runCmd: 'yarn run',
|
|
41
|
+
},
|
|
42
|
+
npm: {
|
|
43
|
+
name: 'npm',
|
|
44
|
+
lockFile: 'package-lock.json',
|
|
45
|
+
execCmd: 'npx',
|
|
46
|
+
installCmd: 'npm install',
|
|
47
|
+
runCmd: 'npm run',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Detect which package manager a project uses.
|
|
53
|
+
*
|
|
54
|
+
* Priority:
|
|
55
|
+
* 1. CLAUDE_PACKAGE_MANAGER env variable
|
|
56
|
+
* 2. Lock file detection (bun > pnpm > yarn > npm)
|
|
57
|
+
* 3. Default to npm
|
|
58
|
+
*
|
|
59
|
+
* @param {{ projectDir?: string }} [options]
|
|
60
|
+
* @returns {{ name: string, config: object }}
|
|
61
|
+
*/
|
|
62
|
+
function getPackageManager(options = {}) {
|
|
63
|
+
const projectDir = options.projectDir || process.cwd();
|
|
64
|
+
|
|
65
|
+
// 1. Environment variable override
|
|
66
|
+
const envPm = process.env.CLAUDE_PACKAGE_MANAGER;
|
|
67
|
+
if (envPm && PM_CONFIGS[envPm]) {
|
|
68
|
+
return { name: envPm, config: PM_CONFIGS[envPm] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Lock file detection (ordered by specificity)
|
|
72
|
+
for (const [name, config] of Object.entries(PM_CONFIGS)) {
|
|
73
|
+
const lockPath = path.join(projectDir, config.lockFile);
|
|
74
|
+
if (fs.existsSync(lockPath)) {
|
|
75
|
+
return { name, config };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Default to npm
|
|
80
|
+
return { name: 'npm', config: PM_CONFIGS.npm };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { getPackageManager, PM_CONFIGS };
|
package/scripts/lib/utils.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const os = require('os');
|
|
9
|
-
const { execSync
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
10
|
|
|
11
11
|
// Platform detection
|
|
12
12
|
const isWindows = process.platform === 'win32';
|
|
@@ -41,13 +41,6 @@ function getLearnedSkillsDir() {
|
|
|
41
41
|
return path.join(getClaudeDir(), 'skills', 'learned');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
45
|
-
* Get the temp directory (cross-platform)
|
|
46
|
-
*/
|
|
47
|
-
function getTempDir() {
|
|
48
|
-
return os.tmpdir();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
44
|
/**
|
|
52
45
|
* Ensure a directory exists (create if not)
|
|
53
46
|
* @param {string} dirPath - Directory path to create
|
|
@@ -119,20 +112,6 @@ function getSessionIdShort(fallback = 'default') {
|
|
|
119
112
|
return getProjectName() || fallback;
|
|
120
113
|
}
|
|
121
114
|
|
|
122
|
-
/**
|
|
123
|
-
* Get current datetime in YYYY-MM-DD HH:MM:SS format
|
|
124
|
-
*/
|
|
125
|
-
function getDateTimeString() {
|
|
126
|
-
const now = new Date();
|
|
127
|
-
const year = now.getFullYear();
|
|
128
|
-
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
129
|
-
const day = String(now.getDate()).padStart(2, '0');
|
|
130
|
-
const hours = String(now.getHours()).padStart(2, '0');
|
|
131
|
-
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
132
|
-
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
133
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
115
|
/**
|
|
137
116
|
* Find files matching a pattern in a directory (cross-platform alternative to find)
|
|
138
117
|
* @param {string} dir - Directory to search
|
|
@@ -296,38 +275,6 @@ function writeFile(filePath, content) {
|
|
|
296
275
|
fs.writeFileSync(filePath, content, 'utf8');
|
|
297
276
|
}
|
|
298
277
|
|
|
299
|
-
/**
|
|
300
|
-
* Append to a text file
|
|
301
|
-
*/
|
|
302
|
-
function appendFile(filePath, content) {
|
|
303
|
-
ensureDir(path.dirname(filePath));
|
|
304
|
-
fs.appendFileSync(filePath, content, 'utf8');
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* Check if a command exists in PATH
|
|
309
|
-
* Uses execFileSync to prevent command injection
|
|
310
|
-
*/
|
|
311
|
-
function commandExists(cmd) {
|
|
312
|
-
// Validate command name - only allow alphanumeric, dash, underscore, dot
|
|
313
|
-
if (!/^[a-zA-Z0-9_.-]+$/.test(cmd)) {
|
|
314
|
-
return false;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
if (isWindows) {
|
|
319
|
-
// Use spawnSync to avoid shell interpolation
|
|
320
|
-
const result = spawnSync('where', [cmd], { stdio: 'pipe' });
|
|
321
|
-
return result.status === 0;
|
|
322
|
-
} else {
|
|
323
|
-
const result = spawnSync('which', [cmd], { stdio: 'pipe' });
|
|
324
|
-
return result.status === 0;
|
|
325
|
-
}
|
|
326
|
-
} catch {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
278
|
/**
|
|
332
279
|
* Run a command and return output
|
|
333
280
|
*
|
|
@@ -405,65 +352,6 @@ function getGitModifiedFiles(patterns = []) {
|
|
|
405
352
|
return files;
|
|
406
353
|
}
|
|
407
354
|
|
|
408
|
-
/**
|
|
409
|
-
* Replace text in a file (cross-platform sed alternative)
|
|
410
|
-
* @param {string} filePath - Path to the file
|
|
411
|
-
* @param {string|RegExp} search - Pattern to search for. String patterns replace
|
|
412
|
-
* the FIRST occurrence only; use a RegExp with the `g` flag for global replacement.
|
|
413
|
-
* @param {string} replace - Replacement string
|
|
414
|
-
* @param {object} options - Options
|
|
415
|
-
* @param {boolean} options.all - When true and search is a string, replaces ALL
|
|
416
|
-
* occurrences (uses String.replaceAll). Ignored for RegExp patterns.
|
|
417
|
-
* @returns {boolean} true if file was written, false on error
|
|
418
|
-
*/
|
|
419
|
-
function replaceInFile(filePath, search, replace, options = {}) {
|
|
420
|
-
const content = readFile(filePath);
|
|
421
|
-
if (content === null) return false;
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
let newContent;
|
|
425
|
-
if (options.all && typeof search === 'string') {
|
|
426
|
-
newContent = content.replaceAll(search, replace);
|
|
427
|
-
} else {
|
|
428
|
-
newContent = content.replace(search, replace);
|
|
429
|
-
}
|
|
430
|
-
writeFile(filePath, newContent);
|
|
431
|
-
return true;
|
|
432
|
-
} catch (err) {
|
|
433
|
-
log(`[Utils] replaceInFile failed for ${filePath}: ${err.message}`);
|
|
434
|
-
return false;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Count occurrences of a pattern in a file
|
|
440
|
-
* @param {string} filePath - Path to the file
|
|
441
|
-
* @param {string|RegExp} pattern - Pattern to count. Strings are treated as
|
|
442
|
-
* global regex patterns. RegExp instances are used as-is but the global
|
|
443
|
-
* flag is enforced to ensure correct counting.
|
|
444
|
-
* @returns {number} Number of matches found
|
|
445
|
-
*/
|
|
446
|
-
function countInFile(filePath, pattern) {
|
|
447
|
-
const content = readFile(filePath);
|
|
448
|
-
if (content === null) return 0;
|
|
449
|
-
|
|
450
|
-
let regex;
|
|
451
|
-
try {
|
|
452
|
-
if (pattern instanceof RegExp) {
|
|
453
|
-
// Always create new RegExp to avoid shared lastIndex state; ensure global flag
|
|
454
|
-
regex = new RegExp(pattern.source, pattern.flags.includes('g') ? pattern.flags : pattern.flags + 'g');
|
|
455
|
-
} else if (typeof pattern === 'string') {
|
|
456
|
-
regex = new RegExp(pattern, 'g');
|
|
457
|
-
} else {
|
|
458
|
-
return 0;
|
|
459
|
-
}
|
|
460
|
-
} catch {
|
|
461
|
-
return 0; // Invalid regex pattern
|
|
462
|
-
}
|
|
463
|
-
const matches = content.match(regex);
|
|
464
|
-
return matches ? matches.length : 0;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
355
|
/**
|
|
468
356
|
* Strip all ANSI escape sequences from a string.
|
|
469
357
|
*
|
|
@@ -482,39 +370,6 @@ function stripAnsi(str) {
|
|
|
482
370
|
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
|
|
483
371
|
}
|
|
484
372
|
|
|
485
|
-
/**
|
|
486
|
-
* Search for pattern in file and return matching lines with line numbers
|
|
487
|
-
*/
|
|
488
|
-
function grepFile(filePath, pattern) {
|
|
489
|
-
const content = readFile(filePath);
|
|
490
|
-
if (content === null) return [];
|
|
491
|
-
|
|
492
|
-
let regex;
|
|
493
|
-
try {
|
|
494
|
-
if (pattern instanceof RegExp) {
|
|
495
|
-
// Always create a new RegExp without the 'g' flag to prevent lastIndex
|
|
496
|
-
// state issues when using .test() in a loop (g flag makes .test() stateful,
|
|
497
|
-
// causing alternating match/miss on consecutive matching lines)
|
|
498
|
-
const flags = pattern.flags.replace('g', '');
|
|
499
|
-
regex = new RegExp(pattern.source, flags);
|
|
500
|
-
} else {
|
|
501
|
-
regex = new RegExp(pattern);
|
|
502
|
-
}
|
|
503
|
-
} catch {
|
|
504
|
-
return []; // Invalid regex pattern
|
|
505
|
-
}
|
|
506
|
-
const lines = content.split('\n');
|
|
507
|
-
const results = [];
|
|
508
|
-
|
|
509
|
-
lines.forEach((line, index) => {
|
|
510
|
-
if (regex.test(line)) {
|
|
511
|
-
results.push({ lineNumber: index + 1, content: line });
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
return results;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
373
|
module.exports = {
|
|
519
374
|
// Platform info
|
|
520
375
|
isWindows,
|
|
@@ -522,17 +377,14 @@ module.exports = {
|
|
|
522
377
|
isLinux,
|
|
523
378
|
|
|
524
379
|
// Directories
|
|
525
|
-
getHomeDir,
|
|
526
380
|
getClaudeDir,
|
|
527
381
|
getSessionsDir,
|
|
528
382
|
getLearnedSkillsDir,
|
|
529
|
-
getTempDir,
|
|
530
383
|
ensureDir,
|
|
531
384
|
|
|
532
385
|
// Date/Time
|
|
533
386
|
getDateString,
|
|
534
387
|
getTimeString,
|
|
535
|
-
getDateTimeString,
|
|
536
388
|
|
|
537
389
|
// Session/Project
|
|
538
390
|
getSessionIdShort,
|
|
@@ -543,10 +395,6 @@ module.exports = {
|
|
|
543
395
|
findFiles,
|
|
544
396
|
readFile,
|
|
545
397
|
writeFile,
|
|
546
|
-
appendFile,
|
|
547
|
-
replaceInFile,
|
|
548
|
-
countInFile,
|
|
549
|
-
grepFile,
|
|
550
398
|
|
|
551
399
|
// String sanitisation
|
|
552
400
|
stripAnsi,
|
|
@@ -557,7 +405,6 @@ module.exports = {
|
|
|
557
405
|
output,
|
|
558
406
|
|
|
559
407
|
// System
|
|
560
|
-
commandExists,
|
|
561
408
|
runCommand,
|
|
562
409
|
isGitRepo,
|
|
563
410
|
getGitModifiedFiles
|