@jeremyy_prt/cc-config 1.1.3 → 1.1.9
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/package.json +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 +27 -15
- package/scripts/statusline/src/lib/spend.ts +8 -3
- package/scripts/statusline/src/lib/usage-limits.ts +50 -11
- package/scripts/statusline-wrapper.cmd +3 -0
- package/scripts/statusline-wrapper.sh +3 -0
- 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
|
|
package/package.json
CHANGED
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
export interface GitStatus {
|
|
4
4
|
branch: string;
|
|
@@ -15,9 +15,25 @@ export interface GitStatus {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function exec(command: string, cwd?: string): { stdout: string; exitCode: number } {
|
|
19
19
|
try {
|
|
20
|
-
const
|
|
20
|
+
const stdout = execSync(command, {
|
|
21
|
+
encoding: "utf-8",
|
|
22
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
23
|
+
cwd: cwd || process.cwd(),
|
|
24
|
+
});
|
|
25
|
+
return { stdout, exitCode: 0 };
|
|
26
|
+
} catch (error: any) {
|
|
27
|
+
return {
|
|
28
|
+
stdout: error.stdout?.toString() || "",
|
|
29
|
+
exitCode: error.status || 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function getGitStatus(cwd?: string): Promise<GitStatus> {
|
|
35
|
+
try {
|
|
36
|
+
const isGitRepo = exec("git rev-parse --git-dir", cwd);
|
|
21
37
|
if (isGitRepo.exitCode !== 0) {
|
|
22
38
|
return {
|
|
23
39
|
branch: "no-git",
|
|
@@ -27,21 +43,17 @@ export async function getGitStatus(): Promise<GitStatus> {
|
|
|
27
43
|
};
|
|
28
44
|
}
|
|
29
45
|
|
|
30
|
-
const branchResult =
|
|
31
|
-
const branch = branchResult.trim() || "detached";
|
|
46
|
+
const branchResult = exec("git branch --show-current", cwd);
|
|
47
|
+
const branch = branchResult.stdout.trim() || "detached";
|
|
32
48
|
|
|
33
|
-
const diffCheck =
|
|
34
|
-
const cachedCheck =
|
|
35
|
-
.quiet()
|
|
36
|
-
.nothrow();
|
|
49
|
+
const diffCheck = exec("git diff-index --quiet HEAD --", cwd);
|
|
50
|
+
const cachedCheck = exec("git diff-index --quiet --cached HEAD --", cwd);
|
|
37
51
|
|
|
38
52
|
if (diffCheck.exitCode !== 0 || cachedCheck.exitCode !== 0) {
|
|
39
|
-
const unstagedDiff =
|
|
40
|
-
const stagedDiff =
|
|
41
|
-
const stagedFilesResult =
|
|
42
|
-
|
|
43
|
-
.text();
|
|
44
|
-
const unstagedFilesResult = await $`git diff --name-only`.quiet().text();
|
|
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;
|
|
45
57
|
|
|
46
58
|
const parseStats = (diff: string) => {
|
|
47
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";
|
|
4
|
-
import {
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
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,9 +22,25 @@ interface CachedUsageLimits {
|
|
|
21
22
|
|
|
22
23
|
const CACHE_DURATION_MS = 60 * 1000; // 1 minute
|
|
23
24
|
|
|
24
|
-
function getCacheFilePath(): string {
|
|
25
|
-
|
|
26
|
-
|
|
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
|
+
}
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
interface Credentials {
|
|
@@ -38,11 +55,29 @@ interface Credentials {
|
|
|
38
55
|
|
|
39
56
|
export async function getCredentials(): Promise<string | null> {
|
|
40
57
|
try {
|
|
41
|
-
|
|
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;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sur Windows/Linux : lire ~/.claude/.credentials.json
|
|
69
|
+
const credentialsPath = join(
|
|
70
|
+
process.env.HOME || process.env.USERPROFILE || "",
|
|
71
|
+
".claude",
|
|
72
|
+
".credentials.json",
|
|
73
|
+
);
|
|
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);
|
|
46
81
|
return creds.claudeAiOauth.accessToken;
|
|
47
82
|
} catch {
|
|
48
83
|
return null;
|
|
@@ -83,7 +118,7 @@ export async function fetchUsageLimits(
|
|
|
83
118
|
async function loadCache(): Promise<CachedUsageLimits | null> {
|
|
84
119
|
try {
|
|
85
120
|
const cacheFile = getCacheFilePath();
|
|
86
|
-
if (!existsSync(cacheFile)) {
|
|
121
|
+
if (!cacheFile || !existsSync(cacheFile)) {
|
|
87
122
|
return null;
|
|
88
123
|
}
|
|
89
124
|
|
|
@@ -105,6 +140,10 @@ async function loadCache(): Promise<CachedUsageLimits | null> {
|
|
|
105
140
|
async function saveCache(data: UsageLimits): Promise<void> {
|
|
106
141
|
try {
|
|
107
142
|
const cacheFile = getCacheFilePath();
|
|
143
|
+
if (!cacheFile) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
108
147
|
const cached: CachedUsageLimits = {
|
|
109
148
|
data,
|
|
110
149
|
timestamp: Date.now(),
|