@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.
Files changed (39) hide show
  1. package/README.md +159 -0
  2. package/agents/corriger-orthographe.md +49 -0
  3. package/agents/explorer-code.md +63 -0
  4. package/agents/explorer-docs.md +87 -0
  5. package/agents/recherche-web.md +46 -0
  6. package/cli.js +213 -0
  7. package/commands/commit.md +47 -0
  8. package/commands/corriger-orthographe.md +59 -0
  9. package/commands/creer-agent.md +126 -0
  10. package/commands/creer-commande.md +225 -0
  11. package/commands/liste-commande.md +103 -0
  12. package/commands/memoire-claude.md +190 -0
  13. package/commands/surveiller-ci.md +65 -0
  14. package/package.json +44 -0
  15. package/scripts/statusline/CLAUDE.md +178 -0
  16. package/scripts/statusline/README.md +105 -0
  17. package/scripts/statusline/biome.json +34 -0
  18. package/scripts/statusline/bun.lockb +0 -0
  19. package/scripts/statusline/data/.gitignore +5 -0
  20. package/scripts/statusline/fixtures/test-input.json +25 -0
  21. package/scripts/statusline/package.json +21 -0
  22. package/scripts/statusline/src/commands/CLAUDE.md +3 -0
  23. package/scripts/statusline/src/commands/spend-month.ts +60 -0
  24. package/scripts/statusline/src/commands/spend-today.ts +42 -0
  25. package/scripts/statusline/src/index.ts +199 -0
  26. package/scripts/statusline/src/lib/context.ts +103 -0
  27. package/scripts/statusline/src/lib/formatters.ts +218 -0
  28. package/scripts/statusline/src/lib/git.ts +100 -0
  29. package/scripts/statusline/src/lib/spend.ts +119 -0
  30. package/scripts/statusline/src/lib/types.ts +25 -0
  31. package/scripts/statusline/src/lib/usage-limits.ts +147 -0
  32. package/scripts/statusline/statusline.config.ts +125 -0
  33. package/scripts/statusline/test.ts +20 -0
  34. package/scripts/statusline/tsconfig.json +27 -0
  35. package/scripts/validate-command.js +707 -0
  36. package/scripts/validate-command.readme.md +283 -0
  37. package/settings.json +42 -0
  38. package/song/finish.mp3 +0 -0
  39. 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
+ }