@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 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.copyFileSync(srcSettings, examplePath);
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.copyFileSync(srcSettings, destSettings);
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 `/Users/jeremy/.claude/commands/`
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 `/Users/jeremy/.claude/agents/`
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@jeremyy_prt/cc-config",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "Configuration personnalisée pour Claude Code avec commandes et agents en français",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -71,7 +71,7 @@ Update your `~/.claude/settings.json`:
71
71
  {
72
72
  "statusLine": {
73
73
  "type": "command",
74
- "command": "bun /Users/melvynx/.claude/scripts/statusline/src/index.ts",
74
+ "command": "${CLAUDE_CONFIG_DIR}/scripts/statusline-wrapper.sh",
75
75
  "padding": 0
76
76
  }
77
77
  }
@@ -1,3 +1,3 @@
1
1
  Important :
2
2
 
3
- This folder only include command that we can run using `/Users/melvynx/.claude/scripts/statusline/package.json` commands. Please don't write anything else here.
3
+ This folder only includes commands that can be run using `~/.claude/scripts/statusline/package.json` commands. Please don't write anything else here.
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { formatCost, formatDuration } from "../lib/formatters";
4
4
  import {
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { formatCost, formatDuration } from "../lib/formatters";
4
4
  import {
@@ -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 Bun.file(transcriptPath).text();
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 projectRoot = join(import.meta.dir, "..", "..");
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 projectRoot = join(import.meta.dir, "..", "..");
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
- // Utiliser __dirname pour Node.js au lieu de import.meta.dir (Bun)
26
- const projectRoot = join(__dirname, "..", "..");
27
- return join(projectRoot, "data", "usage-limits-cache.json");
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
- // Cette commande ne fonctionne que sur macOS
43
- // Sur Windows, retourner null (la statusline affichera sans les limites)
44
- if (process.platform !== "darwin") {
45
- return null;
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
- const result = execSync(
49
- 'security find-generic-password -s "Claude Code-credentials" -w',
50
- { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
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
- const creds: Credentials = JSON.parse(result.trim());
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(),
@@ -0,0 +1,3 @@
1
+ @echo off
2
+ cd /d "%USERPROFILE%\.claude\scripts\statusline"
3
+ "%USERPROFILE%\.claude\scripts\statusline\node_modules\.bin\tsx.cmd" "%USERPROFILE%\.claude\scripts\statusline\src\index.ts"
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+ cd "$HOME/.claude/scripts/statusline"
3
+ "$HOME/.claude/scripts/statusline/node_modules/.bin/tsx" "$HOME/.claude/scripts/statusline/src/index.ts"
@@ -1,4 +1,8 @@
1
- #!/usr/bin/env bun
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 /"}}' | bun validate-command.js
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
- this.logFile = "/Users/melvynx/.claude/security.log";
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
- async logSecurityEvent(command, toolName, result, sessionId = null) {
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
- await Bun.write(this.logFile, logLine, { createPath: true, flag: "a" });
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
- await validator.logSecurityEvent(command, toolName, result, sessionId);
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": "bun /Users/melvynx/.claude/validate-command.js"
63
+ "command": "node ${CLAUDE_CONFIG_DIR}/scripts/validate-command.js"
64
64
  }
65
65
  ]
66
66
  }
package/settings.json CHANGED
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "statusLine": {
16
16
  "type": "command",
17
- "command": "npx tsx ${CLAUDE_CONFIG_DIR}/scripts/statusline/src/index.ts",
17
+ "command": "${CLAUDE_CONFIG_DIR}/scripts/statusline-wrapper.cmd",
18
18
  "padding": 0
19
19
  }
20
20
  }