Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/auth/auth-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export class AuthManager {

async loadConfig(): Promise<AuthConfig> {
const raw = await fs.readFile(this.configPath, 'utf-8');
return JSON.parse(raw);
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || !parsed.userId) {
throw new Error('Invalid config.json format');
}
return parsed as AuthConfig;
}

async saveConfig(config: AuthConfig): Promise<void> {
Expand Down
6 changes: 5 additions & 1 deletion src/auth/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ export async function signInWithOAuth(supabase: SupabaseClient, provider: Provid
return;
}

await open(data.url);
try {
await open(data.url);
} catch {
console.log(`\n🔗 Open this URL in your browser:\n ${data.url}\n`);
}
});

setTimeout(() => {
Expand Down
47 changes: 32 additions & 15 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
#!/usr/bin/env node

// For now, a simple arg-based router without commander dependency
import { createRequire } from 'node:module';

const args = process.argv.slice(2);
const command = args[0];

async function main() {
if (command === '--version' || command === '-v') {
const require = createRequire(import.meta.url);
const pkg = require('../package.json');
console.log(`v${pkg.version}`);
return;
}

if (command === '--help' || command === '-h') {
showHelp();
return;
}

switch (command) {
case 'init':
await import('./commands/init.js');
Expand All @@ -30,22 +43,26 @@ async function main() {
await import('./commands/uninstall.js');
break;
default:
console.log('🎮 My HP/MP - Gamified Usage Dashboard\n');
console.log('Supported: Claude Code, Codex CLI\n');
console.log('Commands:');
console.log(' setup — Configure hooks (Claude Code / Codex CLI)');
console.log(' init — Set up authentication (cross-device sync)');
console.log(' usage — Show detailed usage stats');
console.log(' sync — Manually sync stats to cloud');
console.log(' statusline — Toggle status line on/off (Claude Code only)');
console.log(' locale — Change display language (한국어/English)');
console.log(' uninstall — Remove all hooks and settings');
console.log('\nQuick start:');
console.log(' myhpmp setup # Auto-configure everything');
console.log(' myhpmp locale # Set language');
console.log(' myhpmp init # Enable cloud sync & web dashboard (recommended)');
showHelp();
break;
}
}

function showHelp() {
console.log('🎮 My HP/MP - Gamified Usage Dashboard\n');
console.log('Supported: Claude Code, Codex CLI\n');
console.log('Commands:');
console.log(' setup — Configure hooks (Claude Code / Codex CLI)');
console.log(' init — Set up authentication (cross-device sync)');
console.log(' usage — Show detailed usage stats');
console.log(' sync — Manually sync stats to cloud');
console.log(' statusline — Toggle status line on/off (Claude Code only)');
console.log(' locale — Change display language (한국어/English)');
console.log(' uninstall — Remove all hooks and settings');
console.log('\nQuick start:');
console.log(' myhpmp setup # Auto-configure everything');
console.log(' myhpmp locale # Set language');
console.log(' myhpmp init # Enable cloud sync & web dashboard (recommended)');
}

main().catch(console.error);
1 change: 1 addition & 0 deletions src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ async function main() {
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const authManager = new AuthManager(DATA_DIR);
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.on('SIGINT', () => { console.log('\n❌ Cancelled.'); process.exit(130); });

// Check if already authenticated
if (await authManager.isAuthenticated()) {
Expand Down
1 change: 1 addition & 0 deletions src/commands/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function ask(rl: readline.Interface, question: string): Promise<string> {
async function main() {
const authManager = new AuthManager(DATA_DIR);
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.on('SIGINT', () => { console.log('\n❌ Cancelled.'); process.exit(130); });

console.log('🌍 Select display language:\n');
console.log(' 1) 한국어 (Korean)');
Expand Down
2 changes: 2 additions & 0 deletions src/commands/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ async function setupProvider(providerName: string): Promise<void> {
async function main() {
const providers = listProviders();
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.on('SIGINT', () => { console.log('\n❌ Cancelled.'); process.exit(130); });

console.log('🎮 My HP/MP Setup');
console.log('━'.repeat(30));
Expand Down Expand Up @@ -121,5 +122,6 @@ async function main() {

main().catch((err) => {
console.error('❌ Setup failed:', err.message);
console.error(' Check that your .claude or .codex directory exists and is writable.');
process.exit(1);
});
7 changes: 6 additions & 1 deletion src/commands/statusline-toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ async function main() {
}

main().catch((err) => {
console.error('❌ Failed:', err.message);
const msg = err instanceof Error ? err.message : String(err);
if (msg.includes('ENOENT')) {
console.error('❌ Claude settings not found. Run "myhpmp setup" first.');
} else {
console.error('❌ Failed:', msg);
}
process.exit(1);
});
1 change: 1 addition & 0 deletions src/commands/uninstall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ async function removeProviderConfig(providerName: string): Promise<boolean> {

async function main() {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
rl.on('SIGINT', () => { console.log('\n❌ Cancelled.'); process.exit(130); });

console.log('🗑️ My HP/MP Uninstall');
console.log('━'.repeat(30));
Expand Down
8 changes: 8 additions & 0 deletions src/commands/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ async function main() {
const mpPercent = usage ? utilizationToPercent(usage.sevenDay.utilization) : 100;
const resetMinutes = usage ? resetsAtToMinutes(usage.fiveHour.resetsAt) : 0;

if (stats.totalExp === 0 && stats.totalSessions === 0) {
console.log('👋 Welcome to My HP/MP!\n');
console.log('📝 First steps:');
console.log(' 1) myhpmp setup — Install hooks into Claude Code/Codex');
console.log(' 2) Use your AI tool — EXP tracks automatically');
console.log(' 3) myhpmp init — (Optional) Enable cloud sync\n');
}

const syncActive = await authManager.isAuthenticated();

const output = renderDetailView({
Expand Down
6 changes: 5 additions & 1 deletion src/data/auto-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ async function shouldSync(): Promise<boolean> {
try {
const raw = await fs.readFile(LAST_SYNC_PATH, 'utf-8');
const { timestamp } = JSON.parse(raw);
return Date.now() - timestamp >= SYNC_INTERVAL_MS;
if (typeof timestamp !== 'number' || !Number.isFinite(timestamp)) return true;
const elapsed = Date.now() - timestamp;
// If elapsed is negative (clock went backward), force sync
if (elapsed < 0) return true;
return elapsed >= SYNC_INTERVAL_MS;
} catch {
return true; // No record = should sync
}
Expand Down
4 changes: 3 additions & 1 deletion src/data/local-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ export class LocalStore {
async save(stats: UserStats): Promise<void> {
const dir = path.dirname(this.filePath);
await fs.mkdir(dir, { recursive: true });
await fs.writeFile(this.filePath, JSON.stringify(stats, null, 2), 'utf-8');
const tmpPath = `${this.filePath}.tmp`;
await fs.writeFile(tmpPath, JSON.stringify(stats, null, 2), 'utf-8');
await fs.rename(tmpPath, this.filePath);
}
}
5 changes: 4 additions & 1 deletion src/data/pending-exp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ const MAX_QUEUE_SIZE = 1000;
export async function enqueue(entry: PendingExpEntry): Promise<void> {
const queue = await loadQueue();
if (queue.length >= MAX_QUEUE_SIZE) {
queue.shift(); // Drop oldest entry
queue.shift();
if (process.env.DEBUG?.includes('myhpmp')) {
console.error(`[myhpmp] Pending EXP queue full (${MAX_QUEUE_SIZE}). Oldest entry dropped.`);
}
}
queue.push(entry);
await fs.mkdir(DATA_DIR, { recursive: true });
Expand Down
Loading