@jeremyy_prt/cc-config 1.1.4 → 1.2.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/README.md +6 -1
- package/cli.js +25 -2
- package/commands/liste-commande.md +2 -2
- package/package.json +1 -1
- package/scripts/statusline/README.md +1 -1
- package/scripts/statusline/src/commands/CLAUDE.md +1 -1
- package/scripts/statusline/src/commands/spend-month.ts +1 -1
- package/scripts/statusline/src/commands/spend-today.ts +1 -1
- package/scripts/statusline/src/index.ts +1 -1
- package/scripts/statusline/src/lib/context.ts +2 -1
- package/scripts/statusline/src/lib/git.ts +11 -10
- package/scripts/statusline/src/lib/spend.ts +8 -3
- package/scripts/statusline/src/lib/usage-limits.ts +46 -14
- package/scripts/statusline-wrapper.cmd +3 -0
- package/scripts/statusline-wrapper.sh +3 -0
- package/scripts/validate-command.js +20 -7
- package/scripts/validate-command.readme.md +1 -1
- package/settings.json +1 -1
package/README.md
CHANGED
|
@@ -32,8 +32,13 @@ npx @jeremyy_prt/cc-config setup
|
|
|
32
32
|
**Sécurité:**
|
|
33
33
|
- Hook de validation bash (bloque `rm -rf /`, `sudo`, etc.)
|
|
34
34
|
|
|
35
|
+
**Statusline:**
|
|
36
|
+
- Branche Git avec modifications (`main*`)
|
|
37
|
+
- Contexte utilisé (`23%`)
|
|
38
|
+
- Limites d'usage (`Session: 45%`)
|
|
39
|
+
- Compatible **Mac ET Windows** ✅
|
|
40
|
+
|
|
35
41
|
**Bonus:**
|
|
36
|
-
- Statusline avec Git et coûts
|
|
37
42
|
- Sons de notification
|
|
38
43
|
- Hooks configurés
|
|
39
44
|
|
package/cli.js
CHANGED
|
@@ -76,14 +76,25 @@ function mergeSettings() {
|
|
|
76
76
|
|
|
77
77
|
console.log('⚙️ Configuration des settings...');
|
|
78
78
|
|
|
79
|
+
// Lire et adapter le settings.json selon la plateforme
|
|
80
|
+
let settingsContent = fs.readFileSync(srcSettings, 'utf-8');
|
|
81
|
+
|
|
82
|
+
// Remplacer le wrapper selon la plateforme
|
|
83
|
+
const isWindows = process.platform === 'win32';
|
|
84
|
+
const wrapperFile = isWindows ? 'statusline-wrapper.cmd' : 'statusline-wrapper.sh';
|
|
85
|
+
settingsContent = settingsContent.replace(
|
|
86
|
+
/statusline-wrapper\.(cmd|sh)/g,
|
|
87
|
+
wrapperFile
|
|
88
|
+
);
|
|
89
|
+
|
|
79
90
|
if (fs.existsSync(destSettings)) {
|
|
80
91
|
console.log(' ⚠️ settings.json existe déjà');
|
|
81
92
|
const examplePath = path.join(CLAUDE_DIR, 'settings.example.json');
|
|
82
|
-
fs.
|
|
93
|
+
fs.writeFileSync(examplePath, settingsContent);
|
|
83
94
|
console.log(` → Copié vers settings.example.json`);
|
|
84
95
|
console.log(' → Merge manuel recommandé');
|
|
85
96
|
} else {
|
|
86
|
-
fs.
|
|
97
|
+
fs.writeFileSync(destSettings, settingsContent);
|
|
87
98
|
console.log(' ✓ settings.json installé');
|
|
88
99
|
}
|
|
89
100
|
}
|
|
@@ -303,6 +314,18 @@ function setup() {
|
|
|
303
314
|
// Installer dépendances statusline
|
|
304
315
|
installStatuslineDeps();
|
|
305
316
|
|
|
317
|
+
// Rendre le wrapper statusline exécutable sur Mac/Linux
|
|
318
|
+
if (process.platform !== 'win32') {
|
|
319
|
+
try {
|
|
320
|
+
const wrapperPath = path.join(CLAUDE_DIR, 'scripts', 'statusline-wrapper.sh');
|
|
321
|
+
if (fs.existsSync(wrapperPath)) {
|
|
322
|
+
execSync(`chmod +x "${wrapperPath}"`, { stdio: 'ignore' });
|
|
323
|
+
}
|
|
324
|
+
} catch (err) {
|
|
325
|
+
// Ignore errors
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
306
329
|
// Afficher résumé
|
|
307
330
|
listInstalled();
|
|
308
331
|
|
|
@@ -14,13 +14,13 @@ Tu es un assistant qui liste les commandes et agents disponibles avec leurs exem
|
|
|
14
14
|
- Mode exemples: liste + exemples d'utilisation
|
|
15
15
|
|
|
16
16
|
2. **LISTER LES COMMANDES** :
|
|
17
|
-
- Utiliser `Glob` avec le pattern `*.md` dans
|
|
17
|
+
- Utiliser `Glob` avec le pattern `*.md` dans `~/.claude/commands/`
|
|
18
18
|
- Pour chaque fichier, utiliser `Read` pour lire le frontmatter YAML et le contenu
|
|
19
19
|
- Extraire la description entre les `---`
|
|
20
20
|
- Si mode exemples: extraire aussi les exemples du contenu
|
|
21
21
|
|
|
22
22
|
3. **LISTER LES AGENTS** :
|
|
23
|
-
- Utiliser `Glob` avec le pattern `*.md` dans
|
|
23
|
+
- Utiliser `Glob` avec le pattern `*.md` dans `~/.claude/agents/`
|
|
24
24
|
- Pour chaque fichier, utiliser `Read` pour lire le frontmatter YAML et le contenu
|
|
25
25
|
- Extraire le `name` et `description` entre les `---`
|
|
26
26
|
- Si mode exemples: créer des exemples basés sur la description
|
package/package.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
Important :
|
|
2
2
|
|
|
3
|
-
This folder only
|
|
3
|
+
This folder only includes commands that can be run using `~/.claude/scripts/statusline/package.json` commands. Please don't write anything else here.
|
|
@@ -142,7 +142,7 @@ async function main() {
|
|
|
142
142
|
|
|
143
143
|
await saveSession(input);
|
|
144
144
|
|
|
145
|
-
const git = await getGitStatus();
|
|
145
|
+
const git = await getGitStatus(input.workspace.current_dir);
|
|
146
146
|
const branch = formatBranch(git, defaultConfig.git);
|
|
147
147
|
const dirPath = formatPath(
|
|
148
148
|
input.workspace.current_dir,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
2
3
|
|
|
3
4
|
export interface TokenUsage {
|
|
4
5
|
input_tokens: number;
|
|
@@ -23,7 +24,7 @@ export async function getContextLength(
|
|
|
23
24
|
transcriptPath: string,
|
|
24
25
|
): Promise<number> {
|
|
25
26
|
try {
|
|
26
|
-
const content = await
|
|
27
|
+
const content = await readFile(transcriptPath, "utf-8");
|
|
27
28
|
const lines = content.trim().split("\n");
|
|
28
29
|
|
|
29
30
|
if (lines.length === 0) return 0;
|
|
@@ -15,11 +15,12 @@ export interface GitStatus {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function exec(command: string): { stdout: string; exitCode: number } {
|
|
18
|
+
function exec(command: string, cwd?: string): { stdout: string; exitCode: number } {
|
|
19
19
|
try {
|
|
20
20
|
const stdout = execSync(command, {
|
|
21
21
|
encoding: "utf-8",
|
|
22
22
|
stdio: ["pipe", "pipe", "pipe"],
|
|
23
|
+
cwd: cwd || process.cwd(),
|
|
23
24
|
});
|
|
24
25
|
return { stdout, exitCode: 0 };
|
|
25
26
|
} catch (error: any) {
|
|
@@ -30,9 +31,9 @@ function exec(command: string): { stdout: string; exitCode: number } {
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export async function getGitStatus(): Promise<GitStatus> {
|
|
34
|
+
export async function getGitStatus(cwd?: string): Promise<GitStatus> {
|
|
34
35
|
try {
|
|
35
|
-
const isGitRepo = exec("git rev-parse --git-dir");
|
|
36
|
+
const isGitRepo = exec("git rev-parse --git-dir", cwd);
|
|
36
37
|
if (isGitRepo.exitCode !== 0) {
|
|
37
38
|
return {
|
|
38
39
|
branch: "no-git",
|
|
@@ -42,17 +43,17 @@ export async function getGitStatus(): Promise<GitStatus> {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
const branchResult = exec("git branch --show-current");
|
|
46
|
+
const branchResult = exec("git branch --show-current", cwd);
|
|
46
47
|
const branch = branchResult.stdout.trim() || "detached";
|
|
47
48
|
|
|
48
|
-
const diffCheck = exec("git diff-index --quiet HEAD --");
|
|
49
|
-
const cachedCheck = exec("git diff-index --quiet --cached HEAD --");
|
|
49
|
+
const diffCheck = exec("git diff-index --quiet HEAD --", cwd);
|
|
50
|
+
const cachedCheck = exec("git diff-index --quiet --cached HEAD --", cwd);
|
|
50
51
|
|
|
51
52
|
if (diffCheck.exitCode !== 0 || cachedCheck.exitCode !== 0) {
|
|
52
|
-
const unstagedDiff = exec("git diff --numstat").stdout;
|
|
53
|
-
const stagedDiff = exec("git diff --cached --numstat").stdout;
|
|
54
|
-
const stagedFilesResult = exec("git diff --cached --name-only").stdout;
|
|
55
|
-
const unstagedFilesResult = exec("git diff --name-only").stdout;
|
|
53
|
+
const unstagedDiff = exec("git diff --numstat", cwd).stdout;
|
|
54
|
+
const stagedDiff = exec("git diff --cached --numstat", cwd).stdout;
|
|
55
|
+
const stagedFilesResult = exec("git diff --cached --name-only", cwd).stdout;
|
|
56
|
+
const unstagedFilesResult = exec("git diff --name-only", cwd).stdout;
|
|
56
57
|
|
|
57
58
|
const parseStats = (diff: string) => {
|
|
58
59
|
let added = 0;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import type { HookInput } from "./types";
|
|
5
6
|
|
|
6
7
|
export interface SpendSession {
|
|
@@ -19,7 +20,9 @@ export interface SpendData {
|
|
|
19
20
|
|
|
20
21
|
export function getSpendFilePath(): string {
|
|
21
22
|
// Use the project's data folder instead of ~/.claude
|
|
22
|
-
const
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
const projectRoot = join(__dirname, "..", "..");
|
|
23
26
|
return join(projectRoot, "data", "spend.json");
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -40,7 +43,9 @@ export async function loadSpendData(): Promise<SpendData> {
|
|
|
40
43
|
|
|
41
44
|
export async function saveSpendData(data: SpendData): Promise<void> {
|
|
42
45
|
const spendFile = getSpendFilePath();
|
|
43
|
-
const
|
|
46
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
47
|
+
const __dirname = dirname(__filename);
|
|
48
|
+
const projectRoot = join(__dirname, "..", "..");
|
|
44
49
|
const dataDir = join(projectRoot, "data");
|
|
45
50
|
|
|
46
51
|
if (!existsSync(dataDir)) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
|
|
6
7
|
export interface UsageLimits {
|
|
7
8
|
five_hour: {
|
|
@@ -21,10 +22,25 @@ interface CachedUsageLimits {
|
|
|
21
22
|
|
|
22
23
|
const CACHE_DURATION_MS = 60 * 1000; // 1 minute
|
|
23
24
|
|
|
24
|
-
function getCacheFilePath(): string {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
function getCacheFilePath(): string | null {
|
|
26
|
+
try {
|
|
27
|
+
// Utiliser import.meta.url pour ESM (compatible Node.js et Bun)
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = dirname(__filename);
|
|
30
|
+
const projectRoot = join(__dirname, "..", "..");
|
|
31
|
+
const cacheDir = join(projectRoot, "data");
|
|
32
|
+
|
|
33
|
+
// Créer le dossier data s'il n'existe pas
|
|
34
|
+
if (!existsSync(cacheDir)) {
|
|
35
|
+
const { mkdirSync } = require("node:fs");
|
|
36
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return join(cacheDir, "usage-limits-cache.json");
|
|
40
|
+
} catch {
|
|
41
|
+
// Si erreur, désactiver le cache
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
28
44
|
}
|
|
29
45
|
|
|
30
46
|
interface Credentials {
|
|
@@ -39,17 +55,29 @@ interface Credentials {
|
|
|
39
55
|
|
|
40
56
|
export async function getCredentials(): Promise<string | null> {
|
|
41
57
|
try {
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
// Sur macOS : utiliser le Keychain
|
|
59
|
+
if (process.platform === "darwin") {
|
|
60
|
+
const result = execSync(
|
|
61
|
+
'security find-generic-password -s "Claude Code-credentials" -w',
|
|
62
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
|
|
63
|
+
);
|
|
64
|
+
const creds: Credentials = JSON.parse(result.trim());
|
|
65
|
+
return creds.claudeAiOauth.accessToken;
|
|
46
66
|
}
|
|
47
67
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
// Sur Windows/Linux : lire ~/.claude/.credentials.json
|
|
69
|
+
const credentialsPath = join(
|
|
70
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
71
|
+
".claude",
|
|
72
|
+
".credentials.json",
|
|
51
73
|
);
|
|
52
|
-
|
|
74
|
+
|
|
75
|
+
if (!existsSync(credentialsPath)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const credentialsContent = await readFile(credentialsPath, "utf-8");
|
|
80
|
+
const creds: Credentials = JSON.parse(credentialsContent);
|
|
53
81
|
return creds.claudeAiOauth.accessToken;
|
|
54
82
|
} catch {
|
|
55
83
|
return null;
|
|
@@ -90,7 +118,7 @@ export async function fetchUsageLimits(
|
|
|
90
118
|
async function loadCache(): Promise<CachedUsageLimits | null> {
|
|
91
119
|
try {
|
|
92
120
|
const cacheFile = getCacheFilePath();
|
|
93
|
-
if (!existsSync(cacheFile)) {
|
|
121
|
+
if (!cacheFile || !existsSync(cacheFile)) {
|
|
94
122
|
return null;
|
|
95
123
|
}
|
|
96
124
|
|
|
@@ -112,6 +140,10 @@ async function loadCache(): Promise<CachedUsageLimits | null> {
|
|
|
112
140
|
async function saveCache(data: UsageLimits): Promise<void> {
|
|
113
141
|
try {
|
|
114
142
|
const cacheFile = getCacheFilePath();
|
|
143
|
+
if (!cacheFile) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
115
147
|
const cached: CachedUsageLimits = {
|
|
116
148
|
data,
|
|
117
149
|
timestamp: Date.now(),
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* Claude Code "Before Tools" Hook - Command Validation Script
|
|
@@ -7,7 +11,7 @@
|
|
|
7
11
|
* It receives command data via stdin and returns exit code 0 (allow) or 1 (block).
|
|
8
12
|
*
|
|
9
13
|
* Usage: Called automatically by Claude Code PreToolUse hook
|
|
10
|
-
* Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' |
|
|
14
|
+
* Manual test: echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | node validate-command.js
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
// Comprehensive dangerous command patterns database
|
|
@@ -161,10 +165,10 @@ const SECURITY_RULES = {
|
|
|
161
165
|
|
|
162
166
|
// Safe paths where rm -rf is allowed
|
|
163
167
|
SAFE_RM_PATHS: [
|
|
164
|
-
"/Users/melvynx/Developer/",
|
|
165
168
|
"/tmp/",
|
|
166
169
|
"/var/tmp/",
|
|
167
170
|
process.cwd() + "/", // Current working directory
|
|
171
|
+
// User's home directory subdirectories are checked dynamically
|
|
168
172
|
],
|
|
169
173
|
};
|
|
170
174
|
|
|
@@ -205,7 +209,8 @@ const SAFE_COMMANDS = [
|
|
|
205
209
|
|
|
206
210
|
class CommandValidator {
|
|
207
211
|
constructor() {
|
|
208
|
-
|
|
212
|
+
const homeDir = os.homedir();
|
|
213
|
+
this.logFile = path.join(homeDir, '.claude', 'security.log');
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
/**
|
|
@@ -533,7 +538,7 @@ class CommandValidator {
|
|
|
533
538
|
/**
|
|
534
539
|
* Log security events
|
|
535
540
|
*/
|
|
536
|
-
|
|
541
|
+
logSecurityEvent(command, toolName, result, sessionId = null) {
|
|
537
542
|
const timestamp = new Date().toISOString();
|
|
538
543
|
const logEntry = {
|
|
539
544
|
timestamp,
|
|
@@ -549,7 +554,15 @@ class CommandValidator {
|
|
|
549
554
|
try {
|
|
550
555
|
// Write to log file
|
|
551
556
|
const logLine = JSON.stringify(logEntry) + "\n";
|
|
552
|
-
|
|
557
|
+
|
|
558
|
+
// Create directory if it doesn't exist
|
|
559
|
+
const logDir = path.dirname(this.logFile);
|
|
560
|
+
if (!fs.existsSync(logDir)) {
|
|
561
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Append to log file
|
|
565
|
+
fs.appendFileSync(this.logFile, logLine, 'utf8');
|
|
553
566
|
|
|
554
567
|
// Also output to stderr for immediate visibility
|
|
555
568
|
console.error(
|
|
@@ -668,7 +681,7 @@ async function main() {
|
|
|
668
681
|
const result = validator.validate(command, toolName);
|
|
669
682
|
|
|
670
683
|
// Log the security event
|
|
671
|
-
|
|
684
|
+
validator.logSecurityEvent(command, toolName, result, sessionId);
|
|
672
685
|
|
|
673
686
|
// Output result and exit with appropriate code
|
|
674
687
|
if (result.isValid) {
|
|
@@ -60,7 +60,7 @@ The system is already installed and active in your Claude Code configuration:
|
|
|
60
60
|
"hooks": [
|
|
61
61
|
{
|
|
62
62
|
"type": "command",
|
|
63
|
-
"command": "
|
|
63
|
+
"command": "node ${CLAUDE_CONFIG_DIR}/scripts/validate-command.js"
|
|
64
64
|
}
|
|
65
65
|
]
|
|
66
66
|
}
|