cc-peak-hours 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/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/pull_request_template.md +19 -0
- package/.github/workflows/ci.yml +58 -0
- package/CHANGELOG.md +19 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +9 -0
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/SECURITY.md +33 -0
- package/assets/full-en.svg +7 -0
- package/assets/full-fr.svg +7 -0
- package/assets/gen-svg.sh +94 -0
- package/assets/minimal-offpeak-en.svg +4 -0
- package/assets/minimal-offpeak-fr.svg +4 -0
- package/bin/install.js +159 -0
- package/bin/statusline.sh +557 -0
- package/docs/superpowers/plans/2026-03-31-claude-peak-hours.md +1115 -0
- package/docs/superpowers/specs/2026-03-31-claude-peak-hours-design.md +232 -0
- package/package.json +21 -0
- package/peak-hours.json +6 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
|
|
7
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
8
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, "settings.json");
|
|
9
|
+
const STATUSLINE_DEST = path.join(CLAUDE_DIR, "statusline.sh");
|
|
10
|
+
const STATUSLINE_SRC = path.resolve(__dirname, "statusline.sh");
|
|
11
|
+
|
|
12
|
+
const blue = "\x1b[38;2;0;153;255m";
|
|
13
|
+
const green = "\x1b[38;2;0;175;80m";
|
|
14
|
+
const red = "\x1b[38;2;255;85;85m";
|
|
15
|
+
const yellow = "\x1b[38;2;230;200;0m";
|
|
16
|
+
const dim = "\x1b[2m";
|
|
17
|
+
const reset = "\x1b[0m";
|
|
18
|
+
|
|
19
|
+
function log(msg) { console.log(` ${msg}`); }
|
|
20
|
+
function success(msg) { console.log(` ${green}✓${reset} ${msg}`); }
|
|
21
|
+
function warn(msg) { console.log(` ${yellow}!${reset} ${msg}`); }
|
|
22
|
+
function fail(msg) { console.error(` ${red}✗${reset} ${msg}`); }
|
|
23
|
+
|
|
24
|
+
function checkDeps() {
|
|
25
|
+
const { execSync } = require("child_process");
|
|
26
|
+
const missing = [];
|
|
27
|
+
try { execSync("which jq", { stdio: "ignore" }); } catch { missing.push("jq"); }
|
|
28
|
+
try { execSync("which curl", { stdio: "ignore" }); } catch { missing.push("curl"); }
|
|
29
|
+
return missing;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function uninstall() {
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(` ${blue}claude-peak-hours Uninstaller${reset}`);
|
|
35
|
+
console.log(` ${dim}─────────────────────────────${reset}`);
|
|
36
|
+
console.log();
|
|
37
|
+
|
|
38
|
+
const backup = STATUSLINE_DEST + ".bak";
|
|
39
|
+
if (fs.existsSync(backup)) {
|
|
40
|
+
fs.copyFileSync(backup, STATUSLINE_DEST);
|
|
41
|
+
fs.unlinkSync(backup);
|
|
42
|
+
success(`Restored previous statusline from ${dim}statusline.sh.bak${reset}`);
|
|
43
|
+
} else if (fs.existsSync(STATUSLINE_DEST)) {
|
|
44
|
+
fs.unlinkSync(STATUSLINE_DEST);
|
|
45
|
+
success(`Removed ${dim}statusline.sh${reset}`);
|
|
46
|
+
} else {
|
|
47
|
+
warn("No statusline found — nothing to remove");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
51
|
+
try {
|
|
52
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
53
|
+
if (settings.statusLine) {
|
|
54
|
+
delete settings.statusLine;
|
|
55
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
56
|
+
success(`Removed statusLine from ${dim}settings.json${reset}`);
|
|
57
|
+
} else {
|
|
58
|
+
success("Settings already clean");
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
fail(`Could not parse ${SETTINGS_FILE} — fix it manually`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log();
|
|
67
|
+
log(`${green}Done!${reset} Restart Claude Code to apply changes.`);
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function run() {
|
|
72
|
+
const args = process.argv.slice(2);
|
|
73
|
+
|
|
74
|
+
if (args.includes("--uninstall")) {
|
|
75
|
+
uninstall();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Parse flags
|
|
80
|
+
const mode = args.includes("--full") ? "full" : "minimal";
|
|
81
|
+
const timeFmt = args.includes("--24h") ? "--24h" : args.includes("--12h") ? "--12h" : "";
|
|
82
|
+
let lang = "";
|
|
83
|
+
const langIdx = args.indexOf("--lang");
|
|
84
|
+
if (langIdx !== -1 && args[langIdx + 1]) {
|
|
85
|
+
lang = `--lang ${args[langIdx + 1]}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(` ${blue}claude-peak-hours Installer${reset}`);
|
|
90
|
+
console.log(` ${dim}───────────────────────────${reset}`);
|
|
91
|
+
console.log();
|
|
92
|
+
|
|
93
|
+
const missing = checkDeps();
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
fail(`Missing required dependencies: ${missing.join(", ")}`);
|
|
96
|
+
if (missing.includes("jq")) {
|
|
97
|
+
const hint = process.platform === "darwin"
|
|
98
|
+
? "brew install jq"
|
|
99
|
+
: "sudo apt install jq # or: sudo pacman -S jq";
|
|
100
|
+
log(` ${dim}${hint}${reset}`);
|
|
101
|
+
}
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
success("Dependencies found (jq, curl)");
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
107
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
108
|
+
success(`Created ${CLAUDE_DIR}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const backup = STATUSLINE_DEST + ".bak";
|
|
112
|
+
if (fs.existsSync(STATUSLINE_DEST)) {
|
|
113
|
+
fs.copyFileSync(STATUSLINE_DEST, backup);
|
|
114
|
+
warn(`Backed up existing statusline to ${dim}statusline.sh.bak${reset}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fs.copyFileSync(STATUSLINE_SRC, STATUSLINE_DEST);
|
|
118
|
+
fs.chmodSync(STATUSLINE_DEST, 0o755);
|
|
119
|
+
success(`Installed statusline to ${dim}${STATUSLINE_DEST}${reset}`);
|
|
120
|
+
|
|
121
|
+
let settings = {};
|
|
122
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
123
|
+
try {
|
|
124
|
+
settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
125
|
+
} catch {
|
|
126
|
+
fail(`Could not parse ${SETTINGS_FILE} — fix it manually`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const flags = [
|
|
132
|
+
mode === "full" ? "--full" : "",
|
|
133
|
+
timeFmt,
|
|
134
|
+
lang,
|
|
135
|
+
].filter(Boolean).join(" ");
|
|
136
|
+
|
|
137
|
+
const command = flags
|
|
138
|
+
? `bash "$HOME/.claude/statusline.sh" ${flags}`
|
|
139
|
+
: `bash "$HOME/.claude/statusline.sh"`;
|
|
140
|
+
|
|
141
|
+
settings.statusLine = { type: "command", command };
|
|
142
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
143
|
+
success(`Updated ${dim}settings.json${reset} with statusLine config`);
|
|
144
|
+
|
|
145
|
+
console.log();
|
|
146
|
+
log(` Mode: ${blue}${mode}${reset}`);
|
|
147
|
+
log(` Time: ${blue}${timeFmt || "auto-detect"}${reset}`);
|
|
148
|
+
log(` Language: ${blue}${lang || "auto-detect"}${reset}`);
|
|
149
|
+
console.log();
|
|
150
|
+
log(` ${dim}Reconfigure anytime:${reset}`);
|
|
151
|
+
log(` ${dim} npx cc-peak-hours ${reset}${dim}← minimal${reset}`);
|
|
152
|
+
log(` ${dim} npx cc-peak-hours --full --24h --lang fr ${reset}${dim}← full, 24h, French${reset}`);
|
|
153
|
+
log(` ${dim} npx cc-peak-hours --uninstall ${reset}${dim}← restore previous${reset}`);
|
|
154
|
+
console.log();
|
|
155
|
+
log(`${green}Done!${reset} Restart Claude Code to see your new status line.`);
|
|
156
|
+
console.log();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
run();
|