@jeremyy_prt/cc-config 1.0.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 +159 -0
- package/agents/corriger-orthographe.md +49 -0
- package/agents/explorer-code.md +63 -0
- package/agents/explorer-docs.md +87 -0
- package/agents/recherche-web.md +46 -0
- package/cli.js +213 -0
- package/commands/commit.md +47 -0
- package/commands/corriger-orthographe.md +59 -0
- package/commands/creer-agent.md +126 -0
- package/commands/creer-commande.md +225 -0
- package/commands/liste-commande.md +103 -0
- package/commands/memoire-claude.md +190 -0
- package/commands/surveiller-ci.md +65 -0
- package/package.json +44 -0
- package/scripts/statusline/CLAUDE.md +178 -0
- package/scripts/statusline/README.md +105 -0
- package/scripts/statusline/biome.json +34 -0
- package/scripts/statusline/bun.lockb +0 -0
- package/scripts/statusline/data/.gitignore +5 -0
- package/scripts/statusline/fixtures/test-input.json +25 -0
- package/scripts/statusline/package.json +21 -0
- package/scripts/statusline/src/commands/CLAUDE.md +3 -0
- package/scripts/statusline/src/commands/spend-month.ts +60 -0
- package/scripts/statusline/src/commands/spend-today.ts +42 -0
- package/scripts/statusline/src/index.ts +199 -0
- package/scripts/statusline/src/lib/context.ts +103 -0
- package/scripts/statusline/src/lib/formatters.ts +218 -0
- package/scripts/statusline/src/lib/git.ts +100 -0
- package/scripts/statusline/src/lib/spend.ts +119 -0
- package/scripts/statusline/src/lib/types.ts +25 -0
- package/scripts/statusline/src/lib/usage-limits.ts +147 -0
- package/scripts/statusline/statusline.config.ts +125 -0
- package/scripts/statusline/test.ts +20 -0
- package/scripts/statusline/tsconfig.json +27 -0
- package/scripts/validate-command.js +707 -0
- package/scripts/validate-command.readme.md +283 -0
- package/settings.json +42 -0
- package/song/finish.mp3 +0 -0
- package/song/need-human.mp3 +0 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { HookInput } from "./types";
|
|
5
|
+
|
|
6
|
+
export interface SpendSession {
|
|
7
|
+
id: string;
|
|
8
|
+
cost: number;
|
|
9
|
+
date: string;
|
|
10
|
+
duration_ms: number;
|
|
11
|
+
lines_added: number;
|
|
12
|
+
lines_removed: number;
|
|
13
|
+
cwd: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SpendData {
|
|
17
|
+
sessions: SpendSession[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getSpendFilePath(): string {
|
|
21
|
+
// Use the project's data folder instead of ~/.claude
|
|
22
|
+
const projectRoot = join(import.meta.dir, "..", "..");
|
|
23
|
+
return join(projectRoot, "data", "spend.json");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function loadSpendData(): Promise<SpendData> {
|
|
27
|
+
const spendFile = getSpendFilePath();
|
|
28
|
+
|
|
29
|
+
if (!existsSync(spendFile)) {
|
|
30
|
+
return { sessions: [] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const content = await readFile(spendFile, "utf-8");
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
} catch {
|
|
37
|
+
return { sessions: [] };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function saveSpendData(data: SpendData): Promise<void> {
|
|
42
|
+
const spendFile = getSpendFilePath();
|
|
43
|
+
const projectRoot = join(import.meta.dir, "..", "..");
|
|
44
|
+
const dataDir = join(projectRoot, "data");
|
|
45
|
+
|
|
46
|
+
if (!existsSync(dataDir)) {
|
|
47
|
+
mkdirSync(dataDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await writeFile(spendFile, JSON.stringify(data, null, 2));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function saveSession(input: HookInput): Promise<void> {
|
|
54
|
+
if (
|
|
55
|
+
!input.session_id ||
|
|
56
|
+
input.cost.total_cost_usd === 0 ||
|
|
57
|
+
input.cost.total_cost_usd === null
|
|
58
|
+
) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const data = await loadSpendData();
|
|
63
|
+
const isoDate = new Date().toISOString().split("T")[0];
|
|
64
|
+
|
|
65
|
+
const session: SpendSession = {
|
|
66
|
+
id: input.session_id,
|
|
67
|
+
cost: input.cost.total_cost_usd,
|
|
68
|
+
date: isoDate,
|
|
69
|
+
duration_ms: input.cost.total_duration_ms,
|
|
70
|
+
lines_added: input.cost.total_lines_added,
|
|
71
|
+
lines_removed: input.cost.total_lines_removed,
|
|
72
|
+
cwd: input.cwd,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const existingIndex = data.sessions.findIndex(
|
|
76
|
+
(s) => s.id === input.session_id,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (existingIndex !== -1) {
|
|
80
|
+
data.sessions[existingIndex] = session;
|
|
81
|
+
} else {
|
|
82
|
+
data.sessions.push(session);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await saveSpendData(data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function filterSessionsByDate(
|
|
89
|
+
sessions: SpendSession[],
|
|
90
|
+
startDate: Date,
|
|
91
|
+
endDate?: Date,
|
|
92
|
+
): SpendSession[] {
|
|
93
|
+
return sessions.filter((session) => {
|
|
94
|
+
const sessionDate = new Date(session.date);
|
|
95
|
+
if (endDate) {
|
|
96
|
+
return sessionDate >= startDate && sessionDate <= endDate;
|
|
97
|
+
}
|
|
98
|
+
return sessionDate >= startDate;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function calculateTotalCost(sessions: SpendSession[]): number {
|
|
103
|
+
return sessions.reduce((sum, session) => sum + session.cost, 0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function calculateTotalDuration(sessions: SpendSession[]): number {
|
|
107
|
+
return sessions.reduce((sum, session) => sum + session.duration_ms, 0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getTodayStart(): Date {
|
|
111
|
+
const today = new Date();
|
|
112
|
+
today.setHours(0, 0, 0, 0);
|
|
113
|
+
return today;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getMonthStart(): Date {
|
|
117
|
+
const today = new Date();
|
|
118
|
+
return new Date(today.getFullYear(), today.getMonth(), 1);
|
|
119
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface HookInput {
|
|
2
|
+
session_id: string;
|
|
3
|
+
transcript_path: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
model: {
|
|
6
|
+
id: string;
|
|
7
|
+
display_name: string;
|
|
8
|
+
};
|
|
9
|
+
workspace: {
|
|
10
|
+
current_dir: string;
|
|
11
|
+
project_dir: string;
|
|
12
|
+
};
|
|
13
|
+
version: string;
|
|
14
|
+
output_style: {
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
cost: {
|
|
18
|
+
total_cost_usd: number;
|
|
19
|
+
total_duration_ms: number;
|
|
20
|
+
total_api_duration_ms: number;
|
|
21
|
+
total_lines_added: number;
|
|
22
|
+
total_lines_removed: number;
|
|
23
|
+
};
|
|
24
|
+
exceeds_200k_tokens?: boolean;
|
|
25
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { $ } from "bun";
|
|
5
|
+
|
|
6
|
+
export interface UsageLimits {
|
|
7
|
+
five_hour: {
|
|
8
|
+
utilization: number;
|
|
9
|
+
resets_at: string | null;
|
|
10
|
+
} | null;
|
|
11
|
+
seven_day: {
|
|
12
|
+
utilization: number;
|
|
13
|
+
resets_at: string | null;
|
|
14
|
+
} | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CachedUsageLimits {
|
|
18
|
+
data: UsageLimits;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const CACHE_DURATION_MS = 60 * 1000; // 1 minute
|
|
23
|
+
|
|
24
|
+
function getCacheFilePath(): string {
|
|
25
|
+
const projectRoot = join(import.meta.dir, "..", "..");
|
|
26
|
+
return join(projectRoot, "data", "usage-limits-cache.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Credentials {
|
|
30
|
+
claudeAiOauth: {
|
|
31
|
+
accessToken: string;
|
|
32
|
+
refreshToken: string;
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
scopes: string[];
|
|
35
|
+
subscriptionType: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function getCredentials(): Promise<string | null> {
|
|
40
|
+
try {
|
|
41
|
+
const result =
|
|
42
|
+
await $`security find-generic-password -s "Claude Code-credentials" -w`
|
|
43
|
+
.quiet()
|
|
44
|
+
.text();
|
|
45
|
+
const creds: Credentials = JSON.parse(result.trim());
|
|
46
|
+
return creds.claudeAiOauth.accessToken;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function fetchUsageLimits(
|
|
53
|
+
token: string,
|
|
54
|
+
): Promise<UsageLimits | null> {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
|
|
57
|
+
method: "GET",
|
|
58
|
+
headers: {
|
|
59
|
+
Accept: "application/json, text/plain, */*",
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
"User-Agent": "claude-code/2.0.31",
|
|
62
|
+
Authorization: `Bearer ${token}`,
|
|
63
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
64
|
+
"Accept-Encoding": "gzip, compress, deflate, br",
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
five_hour: data.five_hour || null,
|
|
76
|
+
seven_day: data.seven_day || null,
|
|
77
|
+
};
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function loadCache(): Promise<CachedUsageLimits | null> {
|
|
84
|
+
try {
|
|
85
|
+
const cacheFile = getCacheFilePath();
|
|
86
|
+
if (!existsSync(cacheFile)) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const content = await readFile(cacheFile, "utf-8");
|
|
91
|
+
const cached: CachedUsageLimits = JSON.parse(content);
|
|
92
|
+
|
|
93
|
+
// Check if cache is still valid (< 1 minute old)
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
if (now - cached.timestamp < CACHE_DURATION_MS) {
|
|
96
|
+
return cached;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function saveCache(data: UsageLimits): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
const cacheFile = getCacheFilePath();
|
|
108
|
+
const cached: CachedUsageLimits = {
|
|
109
|
+
data,
|
|
110
|
+
timestamp: Date.now(),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await writeFile(cacheFile, JSON.stringify(cached, null, 2));
|
|
114
|
+
} catch {
|
|
115
|
+
// Fail silently
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function getUsageLimits(): Promise<UsageLimits> {
|
|
120
|
+
try {
|
|
121
|
+
// Try to load from cache first
|
|
122
|
+
const cached = await loadCache();
|
|
123
|
+
if (cached) {
|
|
124
|
+
return cached.data;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cache miss or expired - fetch from API
|
|
128
|
+
const token = await getCredentials();
|
|
129
|
+
|
|
130
|
+
if (!token) {
|
|
131
|
+
return { five_hour: null, seven_day: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const limits = await fetchUsageLimits(token);
|
|
135
|
+
|
|
136
|
+
if (!limits) {
|
|
137
|
+
return { five_hour: null, seven_day: null };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Save to cache
|
|
141
|
+
await saveCache(limits);
|
|
142
|
+
|
|
143
|
+
return limits;
|
|
144
|
+
} catch {
|
|
145
|
+
return { five_hour: null, seven_day: null };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export type Separator =
|
|
2
|
+
| "|"
|
|
3
|
+
| "•"
|
|
4
|
+
| "·"
|
|
5
|
+
| "⋅"
|
|
6
|
+
| "●"
|
|
7
|
+
| "◆"
|
|
8
|
+
| "▪"
|
|
9
|
+
| "▸"
|
|
10
|
+
| "›"
|
|
11
|
+
| "→";
|
|
12
|
+
|
|
13
|
+
export interface StatuslineConfig {
|
|
14
|
+
// Number of lines to display (1, 2, or 3)
|
|
15
|
+
// - 1: Everything on one line (separated by separator)
|
|
16
|
+
// - 2: Path+git on first line, session+limits on second line
|
|
17
|
+
// - 3: Path on first line, session on second line, limits on third line
|
|
18
|
+
numberOfLines: 1 | 2 | 3;
|
|
19
|
+
|
|
20
|
+
// Show model name even when using Sonnet (default model)
|
|
21
|
+
showSonnetModel: boolean;
|
|
22
|
+
|
|
23
|
+
// Path display mode:
|
|
24
|
+
// - "full": Show complete path with ~ substitution
|
|
25
|
+
// - "truncated": Show only last 2 segments
|
|
26
|
+
// - "basename": Show only the directory name
|
|
27
|
+
pathDisplayMode: "full" | "truncated" | "basename";
|
|
28
|
+
|
|
29
|
+
// Git display configuration
|
|
30
|
+
git: {
|
|
31
|
+
// Show current branch name
|
|
32
|
+
showBranch: boolean;
|
|
33
|
+
// Show * indicator when branch has changes
|
|
34
|
+
showDirtyIndicator: boolean;
|
|
35
|
+
// Show added/deleted lines count
|
|
36
|
+
showChanges: boolean;
|
|
37
|
+
// Show staged files count (gray color)
|
|
38
|
+
showStaged: boolean;
|
|
39
|
+
// Show unstaged files count (yellow color)
|
|
40
|
+
showUnstaged: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Separator character between sections
|
|
44
|
+
// Options: "|", "•", "·", "⋅", "●", "◆", "▪", "▸", "›", "→"
|
|
45
|
+
separator: Separator;
|
|
46
|
+
|
|
47
|
+
// Session display configuration
|
|
48
|
+
session: {
|
|
49
|
+
// Separator character between session info (cost, tokens, percentage)
|
|
50
|
+
// Options: "|", "•", "·", "⋅", "●", "◆", "▪", "▸", "›", "→"
|
|
51
|
+
// Use null for single space separator
|
|
52
|
+
infoSeparator: Separator | null;
|
|
53
|
+
// Show session cost in USD
|
|
54
|
+
showCost: boolean;
|
|
55
|
+
// Show token count
|
|
56
|
+
showTokens: boolean;
|
|
57
|
+
// Show max tokens (e.g., "192k/200k" vs "192k")
|
|
58
|
+
showMaxTokens: boolean;
|
|
59
|
+
// Show decimals in token count (e.g., "192.1k" vs "192k")
|
|
60
|
+
showTokenDecimals: boolean;
|
|
61
|
+
// Show context percentage
|
|
62
|
+
showPercentage: boolean;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Context display configuration
|
|
66
|
+
context: {
|
|
67
|
+
// Maximum context window size (Claude's hard limit)
|
|
68
|
+
maxContextTokens: number;
|
|
69
|
+
// Autocompact buffer size (reserved for safety)
|
|
70
|
+
autocompactBufferTokens: number;
|
|
71
|
+
// Use only usable context (includes autocompact buffer in display) vs just transcript
|
|
72
|
+
useUsableContextOnly: boolean;
|
|
73
|
+
// Approximate tokens overhead for system (prompts, tools, memory files)
|
|
74
|
+
// Default ~20k includes: system prompts (~3k) + tools (~12k) + memory (~5k)
|
|
75
|
+
// Set to 0 to show only transcript tokens
|
|
76
|
+
overheadTokens: number;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Limits display configuration
|
|
80
|
+
limits: {
|
|
81
|
+
// Show progress bar instead of just percentage
|
|
82
|
+
showProgressBar: boolean;
|
|
83
|
+
// Progress bar length (number of characters)
|
|
84
|
+
progressBarLength: 5 | 10;
|
|
85
|
+
// Progress bar color mode:
|
|
86
|
+
// - "progressive": Changes color based on usage (gray < 50%, yellow < 70%, orange < 90%, red >= 90%)
|
|
87
|
+
// - "green": Always green
|
|
88
|
+
// - "yellow": Always yellow
|
|
89
|
+
// - "red": Always red
|
|
90
|
+
color: "progressive" | "green" | "yellow" | "red";
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const defaultConfig: StatuslineConfig = {
|
|
95
|
+
numberOfLines: 3,
|
|
96
|
+
showSonnetModel: false,
|
|
97
|
+
pathDisplayMode: "full",
|
|
98
|
+
git: {
|
|
99
|
+
showBranch: true,
|
|
100
|
+
showDirtyIndicator: true,
|
|
101
|
+
showChanges: false,
|
|
102
|
+
showStaged: true,
|
|
103
|
+
showUnstaged: true,
|
|
104
|
+
},
|
|
105
|
+
separator: "•",
|
|
106
|
+
session: {
|
|
107
|
+
infoSeparator: null,
|
|
108
|
+
showCost: false,
|
|
109
|
+
showTokens: false,
|
|
110
|
+
showMaxTokens: false,
|
|
111
|
+
showTokenDecimals: false,
|
|
112
|
+
showPercentage: true,
|
|
113
|
+
},
|
|
114
|
+
context: {
|
|
115
|
+
maxContextTokens: 200000,
|
|
116
|
+
autocompactBufferTokens: 45000,
|
|
117
|
+
useUsableContextOnly: true,
|
|
118
|
+
overheadTokens: 0,
|
|
119
|
+
},
|
|
120
|
+
limits: {
|
|
121
|
+
showProgressBar: true,
|
|
122
|
+
progressBarLength: 5,
|
|
123
|
+
color: "progressive",
|
|
124
|
+
},
|
|
125
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { $ } from "bun";
|
|
5
|
+
|
|
6
|
+
const fixtureFile = process.argv[2] || "fixtures/test-input.json";
|
|
7
|
+
const fixtureFullPath = join(import.meta.dir, fixtureFile);
|
|
8
|
+
|
|
9
|
+
console.log(`\n📝 Testing with fixture: ${fixtureFile}\n`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const content = await Bun.file(fixtureFullPath).text();
|
|
13
|
+
const result = await $`echo ${content} | bun run src/index.ts`.text();
|
|
14
|
+
|
|
15
|
+
console.log(result);
|
|
16
|
+
console.log("\n✅ Test completed successfully!\n");
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("❌ Test failed:", error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
|
|
22
|
+
// Some stricter flags (disabled by default)
|
|
23
|
+
"noUnusedLocals": false,
|
|
24
|
+
"noUnusedParameters": false,
|
|
25
|
+
"noPropertyAccessFromIndexSignature": false
|
|
26
|
+
}
|
|
27
|
+
}
|