@claudeclock/run 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.
@@ -0,0 +1,6 @@
1
+ interface ProgressBarProps {
2
+ progress: number;
3
+ width?: number;
4
+ }
5
+ export declare function ProgressBar({ progress, width }: ProgressBarProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ export function ProgressBar({ progress, width = 20 }) {
4
+ const clamped = Math.max(0, Math.min(1, progress));
5
+ const filled = Math.round(clamped * width);
6
+ const empty = width - filled;
7
+ const percent = Math.round(clamped * 100);
8
+ return (_jsxs(Text, { children: [_jsx(Text, { color: "green", children: '▓'.repeat(filled) }), _jsx(Text, { dimColor: true, children: '░'.repeat(empty) }), _jsxs(Text, { children: [" ", percent, "%"] })] }));
9
+ }
10
+ //# sourceMappingURL=ProgressBar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProgressBar.js","sourceRoot":"","sources":["../../src/components/ProgressBar.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAO3B,MAAM,UAAU,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,GAAG,EAAE,EAAoB;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;IAE1C,OAAO,CACL,MAAC,IAAI,eACH,KAAC,IAAI,IAAC,KAAK,EAAC,OAAO,YAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAQ,EAC/C,KAAC,IAAI,IAAC,QAAQ,kBAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,GAAQ,EACzC,MAAC,IAAI,oBAAG,OAAO,SAAS,IACnB,CACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { PromoStatus } from '../promo.js';
2
+ interface QuickStatusProps {
3
+ status: PromoStatus;
4
+ }
5
+ export declare function QuickStatus({ status }: QuickStatusProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,13 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { formatDuration, formatLocalTime } from '../promo.js';
4
+ export function QuickStatus({ status }) {
5
+ if (!status.hasActivePromo) {
6
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ['\u23F8', " No active Claude promotion"] }), _jsx(Text, { dimColor: true, children: "Check claudeclock.com for updates" })] }));
7
+ }
8
+ if (status.bonusActive) {
9
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "green", bold: true, children: ['\u26A1', " ", status.multiplier, '\u00D7', " ACTIVE ", '\u2014', " ", formatDuration(status.minutesRemaining), " remaining"] }), status.windowEndLocal && (_jsxs(Text, { dimColor: true, children: ["Ends at ", formatLocalTime(status.windowEndLocal)] })), status.promoEndDate && (_jsxs(Text, { dimColor: true, children: ["Promo ends ", status.promoEndDate.toLocaleDateString()] }))] }));
10
+ }
11
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "yellow", children: ['\uD83D\uDE34', " Peak hours ", '\u2014', " ", status.multiplier, '\u00D7', " resumes in ", formatDuration(status.minutesUntilBonus)] }), status.nextBonusStartLocal && (_jsxs(Text, { dimColor: true, children: ["Next bonus at ", formatLocalTime(status.nextBonusStartLocal)] }))] }));
12
+ }
13
+ //# sourceMappingURL=QuickStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuickStatus.js","sourceRoot":"","sources":["../../src/components/QuickStatus.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAM9D,MAAM,UAAU,WAAW,CAAC,EAAE,MAAM,EAAoB;IACtD,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,IAAI,eAAE,QAAQ,mCAAmC,EAClD,KAAC,IAAI,IAAC,QAAQ,wDAAyC,IACnD,CACP,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,IAAI,IAAC,KAAK,EAAC,OAAO,EAAC,IAAI,mBACrB,QAAQ,OAAG,MAAM,CAAC,UAAU,EAAE,QAAQ,cAAU,QAAQ,OAAG,cAAc,CAAC,MAAM,CAAC,gBAAgB,CAAC,kBAC9F,EACN,MAAM,CAAC,cAAc,IAAI,CACxB,MAAC,IAAI,IAAC,QAAQ,+BAAU,eAAe,CAAC,MAAM,CAAC,cAAc,CAAC,IAAQ,CACvE,EACA,MAAM,CAAC,YAAY,IAAI,CACtB,MAAC,IAAI,IAAC,QAAQ,kCAAa,MAAM,CAAC,YAAY,CAAC,kBAAkB,EAAE,IAAQ,CAC5E,IACG,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,aACjB,cAAc,kBAAc,QAAQ,OAAG,MAAM,CAAC,UAAU,EAAE,QAAQ,kBAAc,cAAc,CAAC,MAAM,CAAC,iBAAiB,CAAC,IACpH,EACN,MAAM,CAAC,mBAAmB,IAAI,CAC7B,MAAC,IAAI,IAAC,QAAQ,qCAAgB,eAAe,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAQ,CAClF,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { PromoConfig } from '../promo.js';
2
+ interface WatchDashboardProps {
3
+ config: PromoConfig;
4
+ notify: boolean;
5
+ }
6
+ export declare function WatchDashboard({ config, notify }: WatchDashboardProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,41 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from 'react';
3
+ import { Box, Text, useApp, useInput } from 'ink';
4
+ import process from 'node:process';
5
+ import { getPromoStatus, formatDuration, formatLocalTime } from '../promo.js';
6
+ import { ProgressBar } from './ProgressBar.js';
7
+ import { sendNotification } from '../notify.js';
8
+ export function WatchDashboard({ config, notify }) {
9
+ const { exit } = useApp();
10
+ const [status, setStatus] = useState(() => getPromoStatus(config));
11
+ const prevBonusActive = useRef(status.bonusActive);
12
+ useEffect(() => {
13
+ const timer = setInterval(() => {
14
+ setStatus(getPromoStatus(config));
15
+ }, 1000);
16
+ return () => clearInterval(timer);
17
+ }, [config]);
18
+ useEffect(() => {
19
+ if (!notify)
20
+ return;
21
+ if (prevBonusActive.current !== status.bonusActive) {
22
+ if (status.bonusActive) {
23
+ sendNotification('Claude Clock', '2\u00D7 bonus window is now active!');
24
+ }
25
+ else {
26
+ sendNotification('Claude Clock', 'Bonus window ended.');
27
+ }
28
+ }
29
+ prevBonusActive.current = status.bonusActive;
30
+ }, [status.bonusActive, notify]);
31
+ useInput((input) => {
32
+ if (input === 'q') {
33
+ exit();
34
+ }
35
+ }, { isActive: process.stdin.isTTY === true });
36
+ if (!status.hasActivePromo) {
37
+ return (_jsxs(Box, { borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ['\u26A1', " CLAUDE CLOCK"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "No active promotion" }), _jsxs(Text, { dimColor: true, children: ["q to quit ", '\u00B7', " claudeclock.com"] })] }));
38
+ }
39
+ return (_jsxs(Box, { borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ['\u26A1', " CLAUDE CLOCK"] }), _jsx(Text, { children: " " }), status.bonusActive ? (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "green", bold: true, children: [status.multiplier, '\u00D7', " BONUS ACTIVE"] }), _jsx(Text, { children: " " }), _jsx(ProgressBar, { progress: status.bonusProgress }), _jsx(Text, { children: " " }), status.windowEndLocal && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["Ends: ", _jsx(Text, { bold: true, children: formatLocalTime(status.windowEndLocal) })] }), _jsxs(Text, { children: ["Remaining: ", _jsx(Text, { bold: true, children: formatDuration(status.minutesRemaining) })] })] }))] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "yellow", bold: true, children: ['\uD83D\uDE34', " PEAK HOURS (1", '\u00D7', ")"] }), _jsx(Text, { children: " " }), status.nextBonusStartLocal && (_jsxs(_Fragment, { children: [_jsxs(Text, { children: [status.multiplier, '\u00D7', " in: ", _jsx(Text, { bold: true, children: formatDuration(status.minutesUntilBonus) })] }), _jsxs(Text, { children: ["Resumes: ", _jsx(Text, { bold: true, children: formatLocalTime(status.nextBonusStartLocal) })] })] }))] })), _jsx(Text, { children: " " }), status.promo && (_jsxs(Text, { dimColor: true, children: [status.promo.name, " ", '\u2014', " ends ", status.promoEndDate?.toLocaleDateString()] })), _jsxs(Text, { dimColor: true, children: ["q to quit ", '\u00B7', " claudeclock.com"] })] }));
40
+ }
41
+ //# sourceMappingURL=WatchDashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WatchDashboard.js","sourceRoot":"","sources":["../../src/components/WatchDashboard.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PromoConfig } from './promo.js';
2
+ export declare const FALLBACK_CONFIG: PromoConfig;
3
+ export declare function loadConfig(): Promise<PromoConfig>;
package/dist/config.js ADDED
@@ -0,0 +1,16 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ export const FALLBACK_CONFIG = require('../../../shared/promo-config.json');
4
+ const PROMO_URL = 'https://claudeclock.com/api/promo';
5
+ export async function loadConfig() {
6
+ try {
7
+ const res = await fetch(PROMO_URL, { signal: AbortSignal.timeout(5000) });
8
+ if (!res.ok)
9
+ throw new Error(`HTTP ${res.status}`);
10
+ return (await res.json());
11
+ }
12
+ catch {
13
+ return FALLBACK_CONFIG;
14
+ }
15
+ }
16
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,eAAe,GAAgB,OAAO,CAAC,mCAAmC,CAAC,CAAC;AAEzF,MAAM,SAAS,GAAG,mCAAmC,CAAC;AAEtD,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC1E,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACnD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,eAAe,CAAC;IACzB,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { render } from 'ink';
4
+ import meow from 'meow';
5
+ import { loadConfig } from './config.js';
6
+ import { getPromoStatus } from './promo.js';
7
+ import { QuickStatus } from './components/QuickStatus.js';
8
+ import { WatchDashboard } from './components/WatchDashboard.js';
9
+ const cli = meow(`
10
+ Usage
11
+ $ claudeclock
12
+
13
+ Options
14
+ --watch, -w Live dashboard mode
15
+ --json Machine-readable JSON output
16
+ --notify Enable system notifications (watch mode)
17
+
18
+ Examples
19
+ $ claudeclock
20
+ $ claudeclock --watch --notify
21
+ $ claudeclock --json
22
+ `, {
23
+ importMeta: import.meta,
24
+ flags: {
25
+ watch: {
26
+ type: 'boolean',
27
+ shortFlag: 'w',
28
+ default: false,
29
+ },
30
+ json: {
31
+ type: 'boolean',
32
+ default: false,
33
+ },
34
+ notify: {
35
+ type: 'boolean',
36
+ default: false,
37
+ },
38
+ },
39
+ });
40
+ async function main() {
41
+ const config = await loadConfig();
42
+ const status = getPromoStatus(config);
43
+ if (cli.flags.json) {
44
+ console.log(JSON.stringify(status, null, 2));
45
+ process.exit(0);
46
+ }
47
+ if (cli.flags.watch) {
48
+ render(_jsx(WatchDashboard, { config: config, notify: cli.flags.notify }));
49
+ return;
50
+ }
51
+ // Quick-check mode: render once and exit
52
+ const { unmount, waitUntilExit } = render(_jsx(QuickStatus, { status: status }));
53
+ unmount();
54
+ await waitUntilExit();
55
+ }
56
+ main().catch((err) => {
57
+ console.error(err);
58
+ process.exit(1);
59
+ });
60
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";;AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,MAAM,GAAG,GAAG,IAAI,CACd;;;;;;;;;;;;;CAaD,EACC;IACE,UAAU,EAAE,MAAM,CAAC,IAAI;IACvB,KAAK,EAAE;QACL,KAAK,EAAE;YACL,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,GAAG;YACd,OAAO,EAAE,KAAK;SACf;QACD,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,KAAK;SACf;QACD,MAAM,EAAE;YACN,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,KAAK;SACf;KACF;CACF,CACF,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAEtC,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACpB,MAAM,CAAC,KAAC,cAAc,IAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,GAAI,CAAC,CAAC;QACrE,OAAO;IACT,CAAC;IAED,yCAAyC;IACzC,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC,KAAC,WAAW,IAAC,MAAM,EAAE,MAAM,GAAI,CAAC,CAAC;IAC3E,OAAO,EAAE,CAAC;IACV,MAAM,aAAa,EAAE,CAAC;AACxB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function sendNotification(title: string, message: string): void;
package/dist/notify.js ADDED
@@ -0,0 +1,5 @@
1
+ import notifier from 'node-notifier';
2
+ export function sendNotification(title, message) {
3
+ notifier.notify({ title, message, sound: true });
4
+ }
5
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../src/notify.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,OAAe;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,49 @@
1
+ export interface PeakHours {
2
+ timezone: string;
3
+ weekdaysOnly: boolean;
4
+ start: string;
5
+ end: string;
6
+ }
7
+ export interface Promo {
8
+ id: string;
9
+ name: string;
10
+ description: string;
11
+ multiplier: number;
12
+ startDate: string;
13
+ endDate: string;
14
+ peakHours: PeakHours;
15
+ eligiblePlans: string[];
16
+ infoUrl: string;
17
+ }
18
+ export interface PromoConfig {
19
+ version: number;
20
+ promos: Promo[];
21
+ }
22
+ export interface PromoStatus {
23
+ hasActivePromo: boolean;
24
+ promo: Promo | null;
25
+ bonusActive: boolean;
26
+ multiplier: number;
27
+ minutesRemaining: number;
28
+ minutesUntilBonus: number;
29
+ windowEndLocal: Date | null;
30
+ nextBonusStartLocal: Date | null;
31
+ promoEndDate: Date | null;
32
+ bonusProgress: number;
33
+ }
34
+ /**
35
+ * Find the first promo whose date range contains `now`.
36
+ */
37
+ export declare function getActivePromo(config: PromoConfig, now?: Date): Promo | null;
38
+ /**
39
+ * Get full promo status for the given config and time.
40
+ */
41
+ export declare function getPromoStatus(config: PromoConfig, now?: Date): PromoStatus;
42
+ /**
43
+ * Format a duration in minutes as "Xh Ym".
44
+ */
45
+ export declare function formatDuration(totalMinutes: number): string;
46
+ /**
47
+ * Format a date as local time with timezone name.
48
+ */
49
+ export declare function formatLocalTime(date: Date): string;
package/dist/promo.js ADDED
@@ -0,0 +1,258 @@
1
+ // Core promo status logic with timezone handling.
2
+ // Shared brain used by CLI, web, and menu bar apps.
3
+ /**
4
+ * Find the first promo whose date range contains `now`.
5
+ */
6
+ export function getActivePromo(config, now) {
7
+ const ts = (now ?? new Date()).getTime();
8
+ for (const promo of config.promos) {
9
+ const start = new Date(promo.startDate).getTime();
10
+ const end = new Date(promo.endDate).getTime();
11
+ if (ts >= start && ts <= end) {
12
+ return promo;
13
+ }
14
+ }
15
+ return null;
16
+ }
17
+ /**
18
+ * Parse "HH:MM" into { hours, minutes }.
19
+ */
20
+ function parseTime(time) {
21
+ const [h, m] = time.split(':').map(Number);
22
+ return { hours: h, minutes: m };
23
+ }
24
+ /**
25
+ * Get the current time components in a specific timezone using Intl.
26
+ */
27
+ function getTimeInTimezone(date, timezone) {
28
+ // Use Intl.DateTimeFormat to get components in the target timezone
29
+ const formatter = new Intl.DateTimeFormat('en-US', {
30
+ timeZone: timezone,
31
+ year: 'numeric',
32
+ month: 'numeric',
33
+ day: 'numeric',
34
+ hour: 'numeric',
35
+ minute: 'numeric',
36
+ weekday: 'short',
37
+ hour12: false,
38
+ });
39
+ const parts = formatter.formatToParts(date);
40
+ const get = (type) => parts.find(p => p.type === type)?.value ?? '';
41
+ const year = parseInt(get('year'), 10);
42
+ const month = parseInt(get('month'), 10);
43
+ const day = parseInt(get('day'), 10);
44
+ let hours = parseInt(get('hour'), 10);
45
+ // Intl with hour12:false can return 24 for midnight
46
+ if (hours === 24)
47
+ hours = 0;
48
+ const minutes = parseInt(get('minute'), 10);
49
+ const weekdayStr = get('weekday');
50
+ const dayMap = {
51
+ Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6,
52
+ };
53
+ const dayOfWeek = dayMap[weekdayStr] ?? 0;
54
+ return { year, month, day, hours, minutes, dayOfWeek };
55
+ }
56
+ /**
57
+ * Convert hours and minutes in a given timezone on a given date to a UTC Date.
58
+ *
59
+ * Uses an iterative approach: make a UTC guess, measure how far off it is in
60
+ * the target timezone (including day differences), then correct. A second pass
61
+ * handles any remaining DST-boundary edge cases.
62
+ */
63
+ function timezoneTimeToDate(year, month, day, hours, minutes, timezone) {
64
+ // Start with a naive UTC guess: treat the target local time as if it were UTC
65
+ let guess = new Date(Date.UTC(year, month - 1, day, hours, minutes, 0));
66
+ // Iteratively correct (two passes is enough to converge)
67
+ for (let i = 0; i < 2; i++) {
68
+ const inTz = getTimeInTimezone(guess, timezone);
69
+ // Build a Date representing what the guess looks like in the target timezone,
70
+ // interpreted as UTC, so we can diff full timestamps (not just hour+minute).
71
+ const tzAsUtc = Date.UTC(inTz.year, inTz.month - 1, inTz.day, inTz.hours, inTz.minutes, 0);
72
+ const targetAsUtc = Date.UTC(year, month - 1, day, hours, minutes, 0);
73
+ const diffMs = targetAsUtc - tzAsUtc;
74
+ if (diffMs === 0)
75
+ break;
76
+ guess = new Date(guess.getTime() + diffMs);
77
+ }
78
+ return guess;
79
+ }
80
+ /**
81
+ * Check if a day-of-week (0=Sun, 6=Sat) is a weekday.
82
+ */
83
+ function isWeekday(dayOfWeek) {
84
+ return dayOfWeek >= 1 && dayOfWeek <= 5;
85
+ }
86
+ /**
87
+ * Get full promo status for the given config and time.
88
+ */
89
+ export function getPromoStatus(config, now) {
90
+ const currentTime = now ?? new Date();
91
+ const promo = getActivePromo(config, currentTime);
92
+ const noPromo = {
93
+ hasActivePromo: false,
94
+ promo: null,
95
+ bonusActive: false,
96
+ multiplier: 1,
97
+ minutesRemaining: 0,
98
+ minutesUntilBonus: 0,
99
+ windowEndLocal: null,
100
+ nextBonusStartLocal: null,
101
+ promoEndDate: null,
102
+ bonusProgress: 0,
103
+ };
104
+ if (!promo)
105
+ return noPromo;
106
+ const { peakHours } = promo;
107
+ const tz = peakHours.timezone;
108
+ // Get current time in the peak timezone
109
+ const tzTime = getTimeInTimezone(currentTime, tz);
110
+ const currentMinutesInDay = tzTime.hours * 60 + tzTime.minutes;
111
+ const peakStart = parseTime(peakHours.start);
112
+ const peakEnd = parseTime(peakHours.end);
113
+ const peakStartMinutes = peakStart.hours * 60 + peakStart.minutes;
114
+ const peakEndMinutes = peakEnd.hours * 60 + peakEnd.minutes;
115
+ const promoEndDate = new Date(promo.endDate);
116
+ // Check if we're in a peak window
117
+ const isDuringWeekday = isWeekday(tzTime.dayOfWeek);
118
+ const isInPeakTimeRange = currentMinutesInDay >= peakStartMinutes && currentMinutesInDay < peakEndMinutes;
119
+ // If weekdaysOnly, peak only applies on weekdays
120
+ const peakActive = peakHours.weekdaysOnly
121
+ ? (isDuringWeekday && isInPeakTimeRange)
122
+ : isInPeakTimeRange;
123
+ const bonusActive = !peakActive;
124
+ const multiplier = bonusActive ? promo.multiplier : 1;
125
+ // Calculate timing info
126
+ let minutesRemaining = 0;
127
+ let minutesUntilBonus = 0;
128
+ let windowEndLocal = null;
129
+ let nextBonusStartLocal = null;
130
+ if (bonusActive) {
131
+ // Bonus is active. How long until peak starts?
132
+ // Find the next peak start time
133
+ if (peakHours.weekdaysOnly) {
134
+ // Find next weekday peak start
135
+ let daysUntilNextPeak = 0;
136
+ if (isDuringWeekday && currentMinutesInDay < peakStartMinutes) {
137
+ // Today is a weekday and peak hasn't started yet — peak starts today
138
+ daysUntilNextPeak = 0;
139
+ }
140
+ else if (isDuringWeekday && currentMinutesInDay >= peakEndMinutes) {
141
+ // After peak today. Next peak is tomorrow if weekday, else next Monday
142
+ daysUntilNextPeak = 1;
143
+ let nextDay = (tzTime.dayOfWeek + 1) % 7;
144
+ while (!isWeekday(nextDay)) {
145
+ daysUntilNextPeak++;
146
+ nextDay = (nextDay + 1) % 7;
147
+ }
148
+ }
149
+ else {
150
+ // Weekend — find next Monday
151
+ daysUntilNextPeak = 1;
152
+ let nextDay = (tzTime.dayOfWeek + 1) % 7;
153
+ while (!isWeekday(nextDay)) {
154
+ daysUntilNextPeak++;
155
+ nextDay = (nextDay + 1) % 7;
156
+ }
157
+ }
158
+ // Window end = next peak start (the bonus window ends when peak begins)
159
+ const nextPeakDate = timezoneTimeToDate(tzTime.year, tzTime.month, tzTime.day + daysUntilNextPeak, peakStart.hours, peakStart.minutes, tz);
160
+ // Cap at promo end
161
+ const effectiveEnd = nextPeakDate.getTime() < promoEndDate.getTime()
162
+ ? nextPeakDate : promoEndDate;
163
+ minutesRemaining = Math.max(0, Math.round((effectiveEnd.getTime() - currentTime.getTime()) / 60_000));
164
+ windowEndLocal = effectiveEnd;
165
+ }
166
+ else {
167
+ // Peak applies every day — next peak start is today or tomorrow
168
+ let daysUntilPeak = 0;
169
+ if (currentMinutesInDay >= peakEndMinutes) {
170
+ daysUntilPeak = 1;
171
+ }
172
+ const nextPeakDate = timezoneTimeToDate(tzTime.year, tzTime.month, tzTime.day + daysUntilPeak, peakStart.hours, peakStart.minutes, tz);
173
+ const effectiveEnd = nextPeakDate.getTime() < promoEndDate.getTime()
174
+ ? nextPeakDate : promoEndDate;
175
+ minutesRemaining = Math.max(0, Math.round((effectiveEnd.getTime() - currentTime.getTime()) / 60_000));
176
+ windowEndLocal = effectiveEnd;
177
+ }
178
+ }
179
+ else {
180
+ // Peak is active (standard rate). How long until bonus starts?
181
+ // Bonus starts when peak ends
182
+ const peakEndDate = timezoneTimeToDate(tzTime.year, tzTime.month, tzTime.day, peakEnd.hours, peakEnd.minutes, tz);
183
+ minutesUntilBonus = Math.max(0, Math.round((peakEndDate.getTime() - currentTime.getTime()) / 60_000));
184
+ nextBonusStartLocal = peakEndDate;
185
+ }
186
+ // Calculate bonus progress (0-1) within the current bonus window
187
+ let bonusProgress = 0;
188
+ if (bonusActive && minutesRemaining > 0) {
189
+ // Total bonus window duration depends on context
190
+ // For simplicity: progress = time elapsed in window / total window duration
191
+ // We know the window ends at windowEndLocal. The window started at the last peak end.
192
+ // For a more accurate calculation, find window start
193
+ if (peakHours.weekdaysOnly && !isDuringWeekday) {
194
+ // Weekend: bonus started Friday at peak end (or Saturday midnight)
195
+ // Simplified: just use a ratio based on time until end
196
+ const totalWindowMinutes = calculateBonusWindowDuration(tzTime, peakStartMinutes, peakEndMinutes, peakHours.weekdaysOnly);
197
+ if (totalWindowMinutes > 0) {
198
+ bonusProgress = Math.min(1, 1 - (minutesRemaining / totalWindowMinutes));
199
+ }
200
+ }
201
+ else {
202
+ // Weekday off-peak: window is from peak end to next peak start (next day)
203
+ const totalWindowMinutes = (24 * 60) - (peakEndMinutes - peakStartMinutes);
204
+ if (totalWindowMinutes > 0) {
205
+ bonusProgress = Math.min(1, 1 - (minutesRemaining / totalWindowMinutes));
206
+ }
207
+ }
208
+ }
209
+ return {
210
+ hasActivePromo: true,
211
+ promo,
212
+ bonusActive,
213
+ multiplier,
214
+ minutesRemaining,
215
+ minutesUntilBonus,
216
+ windowEndLocal,
217
+ nextBonusStartLocal,
218
+ promoEndDate,
219
+ bonusProgress,
220
+ };
221
+ }
222
+ /**
223
+ * Estimate total bonus window duration in minutes for progress calculation.
224
+ */
225
+ function calculateBonusWindowDuration(tzTime, peakStartMinutes, peakEndMinutes, weekdaysOnly) {
226
+ if (weekdaysOnly && !isWeekday(tzTime.dayOfWeek)) {
227
+ // Weekend: bonus runs from Friday peak-end through Sunday midnight to Monday peak-start
228
+ // Friday: (24*60 - peakEndMinutes) + Saturday: 24*60 + Sunday: 24*60 + Monday: peakStartMinutes
229
+ // But depends on which day we're on — simplify
230
+ // Fri after peak + Sat + Sun + Mon before peak
231
+ return (24 * 60 - peakEndMinutes) + (2 * 24 * 60) + peakStartMinutes;
232
+ }
233
+ // Weekday: bonus = 24h - peak duration
234
+ return (24 * 60) - (peakEndMinutes - peakStartMinutes);
235
+ }
236
+ /**
237
+ * Format a duration in minutes as "Xh Ym".
238
+ */
239
+ export function formatDuration(totalMinutes) {
240
+ if (totalMinutes <= 0)
241
+ return '0m';
242
+ const hours = Math.floor(totalMinutes / 60);
243
+ const minutes = Math.round(totalMinutes % 60);
244
+ if (hours === 0)
245
+ return `${minutes}m`;
246
+ return `${hours}h ${minutes}m`;
247
+ }
248
+ /**
249
+ * Format a date as local time with timezone name.
250
+ */
251
+ export function formatLocalTime(date) {
252
+ return date.toLocaleTimeString('en-US', {
253
+ hour: 'numeric',
254
+ minute: '2-digit',
255
+ timeZoneName: 'short',
256
+ });
257
+ }
258
+ //# sourceMappingURL=promo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"promo.js","sourceRoot":"","sources":["../src/promo.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@claudeclock/run",
3
+ "version": "1.0.0",
4
+ "description": "See when you're in the Claude 2x bonus window",
5
+ "type": "module",
6
+ "bin": {
7
+ "claudeclock": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "scripts": {
16
+ "build": "rm -rf dist && tsc",
17
+ "dev": "tsx src/index.tsx",
18
+ "test": "vitest run",
19
+ "test:watch": "vitest"
20
+ },
21
+ "keywords": [
22
+ "claude",
23
+ "anthropic",
24
+ "tokens",
25
+ "bonus",
26
+ "cli"
27
+ ],
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "ink": "^5.1.0",
31
+ "meow": "^13.0.0",
32
+ "node-notifier": "^10.0.1",
33
+ "react": "^18.3.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "@types/node-notifier": "^8.0.5",
38
+ "@types/react": "^19.0.0",
39
+ "ink-testing-library": "^4.0.0",
40
+ "tsx": "^4.19.0",
41
+ "typescript": "^5.7.0",
42
+ "vitest": "^3.0.0"
43
+ }
44
+ }