ccclub 0.1.13 → 0.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/dist/index.js +88 -66
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
+
}) : x)(function(x) {
|
|
5
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
+
});
|
|
2
8
|
|
|
3
9
|
// src/index.ts
|
|
4
10
|
import { Command } from "commander";
|
|
@@ -98,75 +104,70 @@ async function requireConfig() {
|
|
|
98
104
|
return config;
|
|
99
105
|
}
|
|
100
106
|
|
|
101
|
-
// src/
|
|
102
|
-
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
107
|
+
// src/hook.ts
|
|
108
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
103
109
|
import { join as join2 } from "path";
|
|
104
110
|
import { homedir as homedir2 } from "os";
|
|
105
111
|
import { existsSync as existsSync2 } from "fs";
|
|
106
|
-
|
|
107
|
-
var
|
|
108
|
-
var
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
<key>Label</key>
|
|
117
|
-
<string>${PLIST_NAME}</string>
|
|
118
|
-
<key>ProgramArguments</key>
|
|
119
|
-
<array>
|
|
120
|
-
<string>/usr/bin/env</string>
|
|
121
|
-
<string>npx</string>
|
|
122
|
-
<string>ccclub</string>
|
|
123
|
-
<string>sync</string>
|
|
124
|
-
<string>--silent</string>
|
|
125
|
-
</array>
|
|
126
|
-
<key>StartInterval</key>
|
|
127
|
-
<integer>3600</integer>
|
|
128
|
-
<key>StandardOutPath</key>
|
|
129
|
-
<string>${logPath}</string>
|
|
130
|
-
<key>StandardErrorPath</key>
|
|
131
|
-
<string>${logPath}</string>
|
|
132
|
-
<key>RunAtLoad</key>
|
|
133
|
-
<true/>
|
|
134
|
-
<key>EnvironmentVariables</key>
|
|
135
|
-
<dict>
|
|
136
|
-
<key>PATH</key>
|
|
137
|
-
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
138
|
-
</dict>
|
|
139
|
-
</dict>
|
|
140
|
-
</plist>`;
|
|
112
|
+
var CLAUDE_SETTINGS_DIR = join2(homedir2(), ".claude");
|
|
113
|
+
var CLAUDE_SETTINGS_PATH = join2(CLAUDE_SETTINGS_DIR, "settings.json");
|
|
114
|
+
var HOOK_COMMAND = "ccclub sync --silent";
|
|
115
|
+
function hasOurHook(settings) {
|
|
116
|
+
const sessionEndHooks = settings.hooks?.SessionEnd;
|
|
117
|
+
if (!Array.isArray(sessionEndHooks)) return false;
|
|
118
|
+
return sessionEndHooks.some((group) => {
|
|
119
|
+
const g = group;
|
|
120
|
+
return g.hooks?.some((h) => h.command === HOOK_COMMAND);
|
|
121
|
+
});
|
|
141
122
|
}
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
123
|
+
async function installHook() {
|
|
124
|
+
try {
|
|
125
|
+
if (!existsSync2(CLAUDE_SETTINGS_DIR)) {
|
|
126
|
+
await mkdir2(CLAUDE_SETTINGS_DIR, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
let settings = {};
|
|
129
|
+
if (existsSync2(CLAUDE_SETTINGS_PATH)) {
|
|
130
|
+
const raw = await readFile2(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
131
|
+
settings = JSON.parse(raw);
|
|
132
|
+
}
|
|
133
|
+
if (hasOurHook(settings)) return true;
|
|
134
|
+
if (!settings.hooks) settings.hooks = {};
|
|
135
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
136
|
+
settings.hooks.SessionEnd.push({
|
|
137
|
+
hooks: [
|
|
138
|
+
{
|
|
139
|
+
type: "command",
|
|
140
|
+
command: HOOK_COMMAND,
|
|
141
|
+
async: true,
|
|
142
|
+
timeout: 30
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
});
|
|
146
|
+
await writeFile2(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
147
147
|
return true;
|
|
148
|
+
} catch {
|
|
149
|
+
return false;
|
|
148
150
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
await writeFile2(PLIST_PATH, getPlist());
|
|
151
|
+
}
|
|
152
|
+
function isHookInstalled() {
|
|
153
153
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
if (!existsSync2(CLAUDE_SETTINGS_PATH)) return false;
|
|
155
|
+
const raw = __require("fs").readFileSync(CLAUDE_SETTINGS_PATH, "utf-8");
|
|
156
|
+
const settings = JSON.parse(raw);
|
|
157
|
+
return hasOurHook(settings);
|
|
157
158
|
} catch {
|
|
159
|
+
return false;
|
|
158
160
|
}
|
|
159
|
-
return true;
|
|
160
161
|
}
|
|
161
162
|
|
|
162
163
|
// src/commands/sync.ts
|
|
163
|
-
import { readFile as
|
|
164
|
+
import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
164
165
|
import { existsSync as existsSync3 } from "fs";
|
|
165
166
|
import chalk from "chalk";
|
|
166
167
|
import ora from "ora";
|
|
167
168
|
|
|
168
169
|
// src/collector.ts
|
|
169
|
-
import { readFile as
|
|
170
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
170
171
|
import { join as join3 } from "path";
|
|
171
172
|
import { homedir as homedir3 } from "os";
|
|
172
173
|
import { glob } from "glob";
|
|
@@ -179,7 +180,7 @@ async function collectUsageEntries() {
|
|
|
179
180
|
const entries = [];
|
|
180
181
|
const seen = /* @__PURE__ */ new Set();
|
|
181
182
|
for (const file of files) {
|
|
182
|
-
const content = await
|
|
183
|
+
const content = await readFile3(file, "utf-8");
|
|
183
184
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
184
185
|
for (const line of lines) {
|
|
185
186
|
let parsed;
|
|
@@ -306,7 +307,7 @@ async function doSync(firstSync = false, silent = false) {
|
|
|
306
307
|
const lastSyncPath = getLastSyncPath();
|
|
307
308
|
let lastSync = null;
|
|
308
309
|
if (existsSync3(lastSyncPath)) {
|
|
309
|
-
lastSync = (await
|
|
310
|
+
lastSync = (await readFile4(lastSyncPath, "utf-8")).trim() || null;
|
|
310
311
|
}
|
|
311
312
|
let blocksToSync;
|
|
312
313
|
if (lastSync && !firstSync) {
|
|
@@ -388,7 +389,7 @@ async function initCommand() {
|
|
|
388
389
|
displayName: displayName.trim(),
|
|
389
390
|
groups: [data.groupCode]
|
|
390
391
|
});
|
|
391
|
-
const
|
|
392
|
+
const hookOk = await installHook();
|
|
392
393
|
spinner.succeed("CCClub initialized!");
|
|
393
394
|
console.log("");
|
|
394
395
|
console.log(chalk2.bold(" Your invite code:"));
|
|
@@ -396,10 +397,10 @@ async function initCommand() {
|
|
|
396
397
|
${data.groupCode}
|
|
397
398
|
`));
|
|
398
399
|
console.log(chalk2.dim(" Share with friends: ") + chalk2.white(`npx ccclub join ${data.groupCode}`));
|
|
399
|
-
if (
|
|
400
|
-
console.log(chalk2.dim(" Auto-sync: on (
|
|
400
|
+
if (hookOk) {
|
|
401
|
+
console.log(chalk2.dim(" Auto-sync: on (via Claude Code hook)"));
|
|
401
402
|
} else {
|
|
402
|
-
console.log(chalk2.dim(' Tip: run "ccclub
|
|
403
|
+
console.log(chalk2.dim(' Tip: run "ccclub hook" to set up auto-sync'));
|
|
403
404
|
}
|
|
404
405
|
console.log("");
|
|
405
406
|
await doSync(true);
|
|
@@ -480,7 +481,7 @@ async function joinCommand(inviteCode) {
|
|
|
480
481
|
displayName,
|
|
481
482
|
groups: [data.groupCode]
|
|
482
483
|
});
|
|
483
|
-
await
|
|
484
|
+
await installHook();
|
|
484
485
|
}
|
|
485
486
|
spinner.succeed(`Joined "${data.groupName}"!`);
|
|
486
487
|
if (!config) {
|
|
@@ -507,6 +508,7 @@ import Table from "cli-table3";
|
|
|
507
508
|
import ora4 from "ora";
|
|
508
509
|
async function rankCommand(options) {
|
|
509
510
|
const config = await requireConfig();
|
|
511
|
+
await doSync(false, true);
|
|
510
512
|
const isGlobal = options.global === true;
|
|
511
513
|
const period = options.period || "daily";
|
|
512
514
|
let codes;
|
|
@@ -538,7 +540,7 @@ async function rankCommand(options) {
|
|
|
538
540
|
printGroup(data, code, period, config);
|
|
539
541
|
if (i < codes.length - 1) console.log("");
|
|
540
542
|
}
|
|
541
|
-
console.log(chalk4.dim("\n Data syncs automatically
|
|
543
|
+
console.log(chalk4.dim("\n Data syncs automatically when each Claude Code session ends."));
|
|
542
544
|
} catch (err) {
|
|
543
545
|
spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
|
|
544
546
|
}
|
|
@@ -710,9 +712,28 @@ async function createGroupCommand() {
|
|
|
710
712
|
}
|
|
711
713
|
}
|
|
712
714
|
|
|
715
|
+
// src/commands/hook.ts
|
|
716
|
+
import chalk8 from "chalk";
|
|
717
|
+
async function hookCommand() {
|
|
718
|
+
if (isHookInstalled()) {
|
|
719
|
+
console.log(chalk8.green(" Claude Code hook already installed."));
|
|
720
|
+
console.log(chalk8.dim(" Usage syncs automatically when each Claude Code session ends."));
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const ok = await installHook();
|
|
724
|
+
if (ok) {
|
|
725
|
+
console.log(chalk8.green(" Claude Code hook installed!"));
|
|
726
|
+
console.log(chalk8.dim(" Usage will sync automatically when each Claude Code session ends."));
|
|
727
|
+
} else {
|
|
728
|
+
console.log(chalk8.red(" Failed to install hook."));
|
|
729
|
+
console.log(chalk8.dim(" You can manually add to ~/.claude/settings.json:"));
|
|
730
|
+
console.log(chalk8.dim(' {"hooks":{"SessionEnd":[{"hooks":[{"type":"command","command":"ccclub sync --silent","async":true}]}]}}'));
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
713
734
|
// src/global-install.ts
|
|
714
735
|
import { exec } from "child_process";
|
|
715
|
-
import
|
|
736
|
+
import chalk9 from "chalk";
|
|
716
737
|
function run(cmd) {
|
|
717
738
|
return new Promise((resolve) => {
|
|
718
739
|
exec(cmd, (err, stdout4) => resolve(err ? "" : stdout4.trim()));
|
|
@@ -721,19 +742,19 @@ function run(cmd) {
|
|
|
721
742
|
async function ensureGlobalInstall() {
|
|
722
743
|
const globalList = await run("npm list -g ccclub --depth=0");
|
|
723
744
|
if (globalList.includes("ccclub@")) return;
|
|
724
|
-
console.log(
|
|
745
|
+
console.log(chalk9.dim("\n Installing ccclub globally so you can run it directly..."));
|
|
725
746
|
const result = await run("npm install -g ccclub");
|
|
726
747
|
if (result) {
|
|
727
|
-
console.log(
|
|
748
|
+
console.log(chalk9.green(" Done!") + chalk9.dim(" You can now use ") + chalk9.white("ccclub") + chalk9.dim(" directly."));
|
|
728
749
|
} else {
|
|
729
|
-
console.log(
|
|
730
|
-
console.log(
|
|
750
|
+
console.log(chalk9.dim(" Could not auto-install. Run manually:"));
|
|
751
|
+
console.log(chalk9.white(" npm install -g ccclub"));
|
|
731
752
|
}
|
|
732
753
|
}
|
|
733
754
|
|
|
734
755
|
// src/index.ts
|
|
735
756
|
var program = new Command();
|
|
736
|
-
program.name("ccclub").description("CCClub - Compare Claude Code usage with friends").version("0.
|
|
757
|
+
program.name("ccclub").description("CCClub - Compare Claude Code usage with friends").version("0.2.0");
|
|
737
758
|
program.hook("postAction", () => ensureGlobalInstall());
|
|
738
759
|
program.command("init").description("Initialize CCClub (one-time setup)").action(initCommand);
|
|
739
760
|
program.command("join").description("Join a friend's group").argument("<invite-code>", "6-character invite code").action(joinCommand);
|
|
@@ -742,4 +763,5 @@ program.command("rank", { isDefault: true }).description("Show leaderboard ranki
|
|
|
742
763
|
program.command("profile").description("View or update your profile").option("-n, --name <name>", "Set display name").option("--avatar <url>", "Set avatar URL (empty string to reset)").option("--public", "Set profile visibility to public").option("--private", "Set profile visibility to private").action(profileCommand);
|
|
743
764
|
program.command("create").description("Create a new group").action(createGroupCommand);
|
|
744
765
|
program.command("show-data").description("Show exactly what data CCClub uploads (privacy audit)").action(showDataCommand);
|
|
766
|
+
program.command("hook").description("Set up Claude Code hook for auto-sync on session end").action(hookCommand);
|
|
745
767
|
program.parse();
|