aiblueprint-cli 1.4.81 → 1.4.82

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 (80) hide show
  1. package/README.md +21 -17
  2. package/agents-config/claude-config/scripts/statusline/data/.gitignore +8 -0
  3. package/agents-config/claude-config/scripts/statusline/data/.gitkeep +0 -0
  4. package/agents-config/claude-config/scripts/statusline/defaults.json +10 -10
  5. package/agents-config/claude-config/scripts/statusline/src/commands/interactive-config.ts +118 -6
  6. package/agents-config/claude-config/scripts/statusline/src/index.ts +100 -9
  7. package/agents-config/claude-config/scripts/statusline/src/lib/config.ts +28 -2
  8. package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/commands/weekly-analysis.ts +108 -0
  9. package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/index.ts +164 -0
  10. package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/types.ts +15 -0
  11. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/migrate-to-sqlite.ts +136 -0
  12. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-day.ts +79 -0
  13. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-month.ts +66 -0
  14. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-project.ts +85 -0
  15. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/database.ts +395 -0
  16. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/index.ts +178 -0
  17. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/payload-logger.ts +163 -0
  18. package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/types.ts +37 -0
  19. package/agents-config/claude-config/scripts/statusline/src/lib/presets.ts +13 -13
  20. package/agents-config/claude-config/scripts/statusline/statusline.config.free.json +4 -1
  21. package/agents-config/claude-config/scripts/statusline/statusline.config.json +44 -23
  22. package/agents-config/scripts/statusline/data/.gitignore +8 -0
  23. package/agents-config/scripts/statusline/data/.gitkeep +0 -0
  24. package/agents-config/scripts/statusline/defaults.json +10 -10
  25. package/agents-config/scripts/statusline/src/commands/interactive-config.ts +118 -6
  26. package/agents-config/scripts/statusline/src/index.ts +100 -9
  27. package/agents-config/scripts/statusline/src/lib/config.ts +28 -2
  28. package/agents-config/scripts/statusline/src/lib/features/limits/commands/weekly-analysis.ts +108 -0
  29. package/agents-config/scripts/statusline/src/lib/features/limits/index.ts +164 -0
  30. package/agents-config/scripts/statusline/src/lib/features/limits/types.ts +15 -0
  31. package/agents-config/scripts/statusline/src/lib/features/spend/commands/migrate-to-sqlite.ts +136 -0
  32. package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-day.ts +79 -0
  33. package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-month.ts +66 -0
  34. package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-project.ts +85 -0
  35. package/agents-config/scripts/statusline/src/lib/features/spend/database.ts +395 -0
  36. package/agents-config/scripts/statusline/src/lib/features/spend/index.ts +178 -0
  37. package/agents-config/scripts/statusline/src/lib/features/spend/payload-logger.ts +163 -0
  38. package/agents-config/scripts/statusline/src/lib/features/spend/types.ts +37 -0
  39. package/agents-config/scripts/statusline/src/lib/presets.ts +13 -13
  40. package/agents-config/scripts/statusline/statusline.config.free.json +4 -1
  41. package/agents-config/scripts/statusline/statusline.config.json +44 -23
  42. package/agents-config/skills/agents-managers/SKILL.md +1 -1
  43. package/agents-config/skills/agents-managers/references/writing-agent-prompts.md +1 -1
  44. package/agents-config/{commands/prompts/create-vitejs-app.md → skills/create-vitejs-app/SKILL.md} +2 -1
  45. package/agents-config/skills/environments-manager/SKILL.md +6 -6
  46. package/agents-config/skills/environments-manager/examples/{claude/commands/dev.md → skills/dev/SKILL.md} +2 -1
  47. package/agents-config/skills/environments-manager/examples/{claude/commands/lint.md → skills/lint/SKILL.md} +2 -1
  48. package/agents-config/skills/environments-manager/examples/{claude/commands/test.md → skills/test/SKILL.md} +2 -1
  49. package/agents-config/skills/environments-manager/examples/{claude/commands/typecheck.md → skills/typecheck/SKILL.md} +2 -1
  50. package/agents-config/skills/environments-manager/references/claude.md +13 -9
  51. package/agents-config/skills/environments-manager/references/cursor.md +2 -2
  52. package/agents-config/{commands/prompts/nextjs-add-prisma-db.md → skills/nextjs-add-prisma-db/SKILL.md} +1 -0
  53. package/agents-config/{commands/prompts/nextjs-setup-better-auth.md → skills/nextjs-setup-better-auth/SKILL.md} +1 -0
  54. package/agents-config/{commands/prompts/nextjs-setup-project.md → skills/nextjs-setup-project/SKILL.md} +1 -0
  55. package/agents-config/{commands/prompts/prompt.md → skills/prompt/SKILL.md} +5 -0
  56. package/agents-config/{commands/prompts/saas-challenge-idea.md → skills/saas-challenge-idea/SKILL.md} +1 -0
  57. package/agents-config/{commands/prompts/saas-create-architecture.md → skills/saas-create-architecture/SKILL.md} +1 -0
  58. package/agents-config/{commands/prompts/saas-create-headline.md → skills/saas-create-headline/SKILL.md} +1 -0
  59. package/agents-config/{commands/prompts/saas-create-landing-copywritting.md → skills/saas-create-landing-copywritting/SKILL.md} +1 -0
  60. package/agents-config/{commands/prompts/saas-create-legals-docs.md → skills/saas-create-legals-docs/SKILL.md} +1 -0
  61. package/agents-config/{commands/prompts/saas-create-logos.md → skills/saas-create-logos/SKILL.md} +1 -0
  62. package/agents-config/{commands/prompts/saas-create-prd.md → skills/saas-create-prd/SKILL.md} +4 -3
  63. package/agents-config/{commands/prompts/saas-create-tasks.md → skills/saas-create-tasks/SKILL.md} +1 -0
  64. package/agents-config/{commands/prompts/saas-define-pricing.md → skills/saas-define-pricing/SKILL.md} +1 -0
  65. package/agents-config/{commands/prompts/saas-find-domain-name.md → skills/saas-find-domain-name/SKILL.md} +1 -0
  66. package/agents-config/{commands/prompts/saas-implement-landing-page.md → skills/saas-implement-landing-page/SKILL.md} +1 -0
  67. package/agents-config/{commands/prompts/setup-tmux.md → skills/setup-tmux/SKILL.md} +5 -0
  68. package/agents-config/{commands/prompts/tools.md → skills/tools/SKILL.md} +1 -0
  69. package/agents-config/skills/ultrathink/SKILL.md +1 -1
  70. package/agents-config/skills/use-style/SKILL.md +7 -4
  71. package/agents-config/skills/use-style/examples/luma.html +221 -0
  72. package/agents-config/skills/use-style/examples/testspirite.html +340 -0
  73. package/agents-config/skills/use-style/styles/ios-app.md +350 -0
  74. package/agents-config/skills/use-style/styles/luma.md +422 -0
  75. package/agents-config/skills/use-style/styles/new-york-times.md +1 -1
  76. package/agents-config/skills/use-style/styles/split-auth.md +2 -2
  77. package/agents-config/skills/use-style/styles/testspirite.md +397 -0
  78. package/dist/cli.js +17 -80
  79. package/package.json +1 -1
  80. package/agents-config/claude-config/scripts/.claude/commands/fix-on-my-computer.md +0 -87
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- import { readFile } from "node:fs/promises";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
- import { defaultConfig, type StatuslineConfig } from "./lib/config";
5
+ import {
6
+ loadConfig as loadStatuslineConfig,
7
+ type StatuslineConfig,
8
+ } from "./lib/config";
6
9
  import { getContextData } from "./lib/context";
7
10
  import {
8
11
  colors,
@@ -19,6 +22,45 @@ import {
19
22
  } from "./lib/render-pure";
20
23
  import type { HookInput } from "./lib/types";
21
24
 
25
+ // Optional feature imports - just delete the folder to disable!
26
+ let getUsageLimits: any = null;
27
+ let normalizeResetsAt: any = null;
28
+ let getPeriodCost: any = null;
29
+ let getTodayCostV2: any = null;
30
+ let saveSessionV2: any = null;
31
+
32
+ try {
33
+ const limitsModule = await import("./lib/features/limits");
34
+ getUsageLimits = limitsModule.getUsageLimits;
35
+ } catch {
36
+ // Limits feature not available - that's OK!
37
+ }
38
+
39
+ try {
40
+ const utilsModule = await import("./lib/utils");
41
+ normalizeResetsAt = utilsModule.normalizeResetsAt;
42
+ } catch {
43
+ // Fallback normalizeResetsAt
44
+ normalizeResetsAt = (resetsAt: string) => resetsAt;
45
+ }
46
+
47
+ let getTodayRealCost: (() => number) | null = null;
48
+ try {
49
+ const spendModule = await import("./lib/features/spend");
50
+ getPeriodCost = spendModule.getPeriodCost;
51
+ getTodayCostV2 = spendModule.getTodayCostV2;
52
+ saveSessionV2 = spendModule.saveSessionV2;
53
+ } catch {
54
+ // Spend tracking feature not available - that's OK!
55
+ }
56
+
57
+ try {
58
+ const loggerModule = await import("./lib/features/spend/payload-logger");
59
+ getTodayRealCost = loggerModule.getTodayRealCost;
60
+ } catch {
61
+ // Payload logger not available
62
+ }
63
+
22
64
  // Re-export from render-pure for backwards compatibility
23
65
  export {
24
66
  renderStatusline,
@@ -26,7 +68,12 @@ export {
26
68
  type UsageLimit,
27
69
  } from "./lib/render-pure";
28
70
 
29
- const CONFIG_FILE_PATH = join(import.meta.dir, "..", "statusline.config.json");
71
+ const LAST_PAYLOAD_PATH = join(
72
+ import.meta.dir,
73
+ "..",
74
+ "data",
75
+ "last_payload.txt",
76
+ );
30
77
  const CLAUDE_SETTINGS_PATH = join(
31
78
  process.env.HOME || "",
32
79
  ".claude",
@@ -48,21 +95,30 @@ async function loadClaudeSettings(): Promise<ClaudeSettings> {
48
95
  }
49
96
 
50
97
  async function loadConfig(): Promise<StatuslineConfig> {
51
- try {
52
- const content = await readFile(CONFIG_FILE_PATH, "utf-8");
53
- return JSON.parse(content);
54
- } catch {
55
- return defaultConfig;
56
- }
98
+ return loadStatuslineConfig();
57
99
  }
58
100
 
59
101
  async function main() {
60
102
  try {
61
103
  const input: HookInput = await Bun.stdin.json();
62
104
 
105
+ // Save last payload for debugging
106
+ await writeFile(LAST_PAYLOAD_PATH, JSON.stringify(input, null, 2));
107
+
63
108
  const config = await loadConfig();
64
109
  const claudeSettings = await loadClaudeSettings();
65
110
 
111
+ // Get usage limits (if feature exists)
112
+ const usageLimits = getUsageLimits
113
+ ? await getUsageLimits()
114
+ : { five_hour: null, seven_day: null };
115
+ const currentResetsAt = usageLimits.five_hour?.resets_at ?? undefined;
116
+
117
+ // Save session with current period context (if feature exists)
118
+ if (saveSessionV2) {
119
+ await saveSessionV2(input, currentResetsAt);
120
+ }
121
+
66
122
  const git = await getGitStatus();
67
123
 
68
124
  let contextTokens: number | null;
@@ -102,6 +158,24 @@ async function main() {
102
158
  contextPercentage = contextData.percentage;
103
159
  }
104
160
 
161
+ // Get period cost from SQLite (if feature exists)
162
+ let periodCost: number | undefined;
163
+ let todayCost: number | undefined;
164
+
165
+ if (getPeriodCost && normalizeResetsAt) {
166
+ const normalizedPeriodId = currentResetsAt
167
+ ? normalizeResetsAt(currentResetsAt)
168
+ : null;
169
+ periodCost = normalizedPeriodId ? getPeriodCost(normalizedPeriodId) : 0;
170
+ }
171
+
172
+ // Get today's cost from payloads (more accurate than DB)
173
+ if (getTodayRealCost) {
174
+ todayCost = getTodayRealCost();
175
+ } else if (getTodayCostV2) {
176
+ todayCost = getTodayCostV2();
177
+ }
178
+
105
179
  const data: StatuslineData = {
106
180
  branch: formatBranch(git, config.git),
107
181
  dirPath: formatPath(input.workspace.current_dir, config.pathDisplayMode),
@@ -121,6 +195,23 @@ async function main() {
121
195
  sessionDuration: formatDuration(input.cost.total_duration_ms),
122
196
  contextTokens,
123
197
  contextPercentage,
198
+ ...(getUsageLimits && {
199
+ usageLimits: {
200
+ five_hour: usageLimits.five_hour
201
+ ? {
202
+ utilization: usageLimits.five_hour.utilization,
203
+ resets_at: usageLimits.five_hour.resets_at,
204
+ }
205
+ : null,
206
+ seven_day: usageLimits.seven_day
207
+ ? {
208
+ utilization: usageLimits.seven_day.utilization,
209
+ resets_at: usageLimits.seven_day.resets_at,
210
+ }
211
+ : null,
212
+ },
213
+ }),
214
+ ...((getPeriodCost || getTodayCostV2) && { periodCost, todayCost }),
124
215
  thinkingEnabled: claudeSettings.alwaysThinkingEnabled ?? true,
125
216
  };
126
217
 
@@ -10,11 +10,37 @@ export const defaultConfig: StatuslineConfig = JSON.parse(
10
10
  readFileSync(DEFAULTS_PATH, "utf-8"),
11
11
  );
12
12
 
13
+ function cloneDefaultConfig(): StatuslineConfig {
14
+ return JSON.parse(JSON.stringify(defaultConfig));
15
+ }
16
+
17
+ function isRecord(value: unknown): value is Record<string, unknown> {
18
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+
21
+ function mergeConfigDefaults<T>(defaults: T, overrides: unknown): T {
22
+ if (!isRecord(defaults) || !isRecord(overrides)) {
23
+ return overrides === undefined ? defaults : (overrides as T);
24
+ }
25
+
26
+ const merged: Record<string, unknown> = { ...defaults };
27
+ for (const [key, value] of Object.entries(overrides)) {
28
+ merged[key] = mergeConfigDefaults(
29
+ (defaults as Record<string, unknown>)[key],
30
+ value,
31
+ );
32
+ }
33
+ return merged as T;
34
+ }
35
+
13
36
  export function loadConfig(): StatuslineConfig {
14
37
  try {
15
- return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
38
+ return mergeConfigDefaults(
39
+ defaultConfig,
40
+ JSON.parse(readFileSync(CONFIG_PATH, "utf-8")),
41
+ );
16
42
  } catch {
17
- return JSON.parse(JSON.stringify(defaultConfig));
43
+ return cloneDefaultConfig();
18
44
  }
19
45
  }
20
46
 
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { colors, formatProgressBar } from "../../../formatters";
4
+ import { getUsageLimits } from "../index";
5
+
6
+ const WEEKLY_HOURS = 168; // 7 days * 24 hours
7
+
8
+ function calculateTimeProgress(resetsAt: string | null): {
9
+ hoursRemaining: number;
10
+ hoursElapsed: number;
11
+ timeElapsedPercent: number;
12
+ } {
13
+ if (!resetsAt) {
14
+ return {
15
+ hoursRemaining: 0,
16
+ hoursElapsed: WEEKLY_HOURS,
17
+ timeElapsedPercent: 100,
18
+ };
19
+ }
20
+
21
+ const resetDate = new Date(resetsAt);
22
+ const now = new Date();
23
+ const diffMs = resetDate.getTime() - now.getTime();
24
+ const hoursRemaining = Math.max(0, diffMs / 3600000);
25
+ const hoursElapsed = WEEKLY_HOURS - hoursRemaining;
26
+ const timeElapsedPercent = (hoursElapsed / WEEKLY_HOURS) * 100;
27
+
28
+ return { hoursRemaining, hoursElapsed, timeElapsedPercent };
29
+ }
30
+
31
+ function formatDelta(delta: number): string {
32
+ const sign = delta >= 0 ? "+" : "";
33
+ const value = `${sign}${delta.toFixed(1)}%`;
34
+
35
+ if (delta > 5) return colors.green(value);
36
+ if (delta > 0) return colors.lightGray(value);
37
+ if (delta > -5) return colors.yellow(value);
38
+ if (delta > -15) return colors.orange(value);
39
+ return colors.red(value);
40
+ }
41
+
42
+ async function main() {
43
+ const limits = await getUsageLimits();
44
+
45
+ console.log("");
46
+ console.log(colors.bold("📊 Weekly Usage Analysis"));
47
+ console.log(colors.gray("─".repeat(40)));
48
+
49
+ if (!limits.seven_day) {
50
+ console.log(colors.red("No weekly limit data available"));
51
+ return;
52
+ }
53
+
54
+ const { utilization, resets_at } = limits.seven_day;
55
+ const { timeElapsedPercent } = calculateTimeProgress(resets_at);
56
+
57
+ const expectedUsage = timeElapsedPercent;
58
+ const delta = utilization - expectedUsage;
59
+
60
+ console.log("");
61
+ console.log(colors.gray("Weekly Limits Overview"));
62
+ console.log(colors.gray("─".repeat(50)));
63
+ console.log("");
64
+
65
+ const usageBar = formatProgressBar({
66
+ percentage: utilization,
67
+ length: 20,
68
+ style: "filled",
69
+ colorMode: "blue",
70
+ background: "none",
71
+ });
72
+ console.log(
73
+ `${colors.blue("Usage")} ${usageBar} ${colors.lightGray(utilization.toFixed(1))}${colors.gray("%")}`,
74
+ );
75
+
76
+ const timeBar = formatProgressBar({
77
+ percentage: timeElapsedPercent,
78
+ length: 20,
79
+ style: "filled",
80
+ colorMode: "purple",
81
+ background: "none",
82
+ });
83
+ console.log(
84
+ `${colors.purple("Time ")} ${timeBar} ${colors.lightGray(timeElapsedPercent.toFixed(1))}${colors.gray("%")} ${colors.gray("·")} ${formatDelta(delta)}`,
85
+ );
86
+
87
+ console.log("");
88
+ console.log(colors.gray("─".repeat(50)));
89
+ console.log("");
90
+
91
+ if (limits.five_hour) {
92
+ console.log(colors.gray("─".repeat(40)));
93
+ console.log("");
94
+ console.log(colors.gray("Session Limit (5h):"));
95
+ console.log(
96
+ ` ${formatProgressBar({
97
+ percentage: limits.five_hour.utilization,
98
+ length: 15,
99
+ style: "filled",
100
+ colorMode: "progressive",
101
+ background: "none",
102
+ })} ${colors.lightGray(limits.five_hour.utilization.toString())}${colors.gray("%")}`,
103
+ );
104
+ console.log("");
105
+ }
106
+ }
107
+
108
+ main();
@@ -0,0 +1,164 @@
1
+ import { execFile } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { promisify } from "node:util";
7
+ import type { CachedUsageLimits, UsageLimits } from "./types";
8
+
9
+ const CACHE_DURATION_MS = 60 * 1000; // 1 minute
10
+ const execFileAsync = promisify(execFile);
11
+
12
+ function getCacheFilePath(): string {
13
+ const projectRoot = join(import.meta.dir, "..", "..", "..", "..");
14
+ return join(projectRoot, "data", "usage-limits-cache.json");
15
+ }
16
+
17
+ export async function getCredentials(): Promise<string | null> {
18
+ return (
19
+ (await getClaudeCodeTokenFromKeychain()) ?? getClaudeCodeTokenFromFile()
20
+ );
21
+ }
22
+
23
+ async function getClaudeCodeTokenFromKeychain(): Promise<string | null> {
24
+ if (process.platform !== "darwin") {
25
+ return null;
26
+ }
27
+
28
+ try {
29
+ const { stdout } = await execFileAsync("security", [
30
+ "find-generic-password",
31
+ "-s",
32
+ "Claude Code-credentials",
33
+ "-w",
34
+ ]);
35
+
36
+ return parseClaudeCodeToken(stdout.trim());
37
+ } catch {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ async function getClaudeCodeTokenFromFile(): Promise<string | null> {
43
+ try {
44
+ const credentialsPath = join(homedir(), ".claude", ".credentials.json");
45
+ if (!existsSync(credentialsPath)) {
46
+ return null;
47
+ }
48
+
49
+ const content = await readFile(credentialsPath, "utf-8");
50
+ return parseClaudeCodeToken(content);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function parseClaudeCodeToken(content: string): string | null {
57
+ if (!content) {
58
+ return null;
59
+ }
60
+
61
+ try {
62
+ const credentials = JSON.parse(content);
63
+ return (
64
+ credentials.claudeAiOauth?.accessToken ?? credentials.accessToken ?? null
65
+ );
66
+ } catch {
67
+ return content.startsWith("sk-") ? content : null;
68
+ }
69
+ }
70
+
71
+ export async function fetchUsageLimits(
72
+ token: string,
73
+ ): Promise<UsageLimits | null> {
74
+ try {
75
+ const response = await fetch("https://api.anthropic.com/api/oauth/usage", {
76
+ method: "GET",
77
+ headers: {
78
+ Accept: "application/json, text/plain, */*",
79
+ "Content-Type": "application/json",
80
+ "User-Agent": "claude-code/2.0.31",
81
+ Authorization: `Bearer ${token}`,
82
+ "anthropic-beta": "oauth-2025-04-20",
83
+ "Accept-Encoding": "gzip, compress, deflate, br",
84
+ },
85
+ });
86
+
87
+ if (!response.ok) {
88
+ return null;
89
+ }
90
+
91
+ const data = await response.json();
92
+
93
+ return {
94
+ five_hour: data.five_hour || null,
95
+ seven_day: data.seven_day || null,
96
+ };
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ async function loadCache(): Promise<CachedUsageLimits | null> {
103
+ try {
104
+ const cacheFile = getCacheFilePath();
105
+ if (!existsSync(cacheFile)) {
106
+ return null;
107
+ }
108
+
109
+ const content = await readFile(cacheFile, "utf-8");
110
+ const cached: CachedUsageLimits = JSON.parse(content);
111
+
112
+ const now = Date.now();
113
+ if (now - cached.timestamp < CACHE_DURATION_MS) {
114
+ return cached;
115
+ }
116
+
117
+ return null;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ async function saveCache(data: UsageLimits): Promise<void> {
124
+ try {
125
+ const cacheFile = getCacheFilePath();
126
+ const cached: CachedUsageLimits = {
127
+ data,
128
+ timestamp: Date.now(),
129
+ };
130
+
131
+ await writeFile(cacheFile, JSON.stringify(cached, null, 2));
132
+ } catch {
133
+ // Fail silently
134
+ }
135
+ }
136
+
137
+ export async function getUsageLimits(): Promise<UsageLimits> {
138
+ try {
139
+ const cached = await loadCache();
140
+ if (cached) {
141
+ return cached.data;
142
+ }
143
+
144
+ const token = await getCredentials();
145
+
146
+ if (!token) {
147
+ return { five_hour: null, seven_day: null };
148
+ }
149
+
150
+ const limits = await fetchUsageLimits(token);
151
+
152
+ if (!limits) {
153
+ return { five_hour: null, seven_day: null };
154
+ }
155
+
156
+ await saveCache(limits);
157
+
158
+ return limits;
159
+ } catch {
160
+ return { five_hour: null, seven_day: null };
161
+ }
162
+ }
163
+
164
+ export type { CachedUsageLimits, UsageLimits } from "./types";
@@ -0,0 +1,15 @@
1
+ export interface UsageLimits {
2
+ five_hour: {
3
+ utilization: number;
4
+ resets_at: string | null;
5
+ } | null;
6
+ seven_day: {
7
+ utilization: number;
8
+ resets_at: string | null;
9
+ } | null;
10
+ }
11
+
12
+ export interface CachedUsageLimits {
13
+ data: UsageLimits;
14
+ timestamp: number;
15
+ }
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { readFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { getDb, upsertPeriod, upsertSession } from "../database";
7
+
8
+ interface OldSpendSession {
9
+ id: string;
10
+ cost: number;
11
+ date: string;
12
+ duration_ms: number;
13
+ lines_added: number;
14
+ lines_removed: number;
15
+ cwd: string;
16
+ last_resets_at?: string;
17
+ }
18
+
19
+ interface OldSpendData {
20
+ sessions: OldSpendSession[];
21
+ }
22
+
23
+ interface OldDailyUsageEntry {
24
+ date: string;
25
+ utilization: number;
26
+ resets_at: string;
27
+ timestamp: number;
28
+ period_cost: number;
29
+ }
30
+
31
+ async function loadOldSpendData(): Promise<OldSpendData | null> {
32
+ const projectRoot = join(import.meta.dir, "..", "..", "..", "..", "..");
33
+ const spendFile = join(projectRoot, "data", "spend.json");
34
+
35
+ if (!existsSync(spendFile)) {
36
+ console.log("No spend.json found, skipping session migration");
37
+ return null;
38
+ }
39
+
40
+ const content = await readFile(spendFile, "utf-8");
41
+ return JSON.parse(content);
42
+ }
43
+
44
+ async function loadOldDailyUsage(): Promise<OldDailyUsageEntry[]> {
45
+ const projectRoot = join(import.meta.dir, "..", "..", "..", "..", "..");
46
+ const dailyUsageFile = join(projectRoot, "data", "daily-usage.json");
47
+
48
+ if (!existsSync(dailyUsageFile)) {
49
+ console.log("No daily-usage.json found, skipping period migration");
50
+ return [];
51
+ }
52
+
53
+ const content = await readFile(dailyUsageFile, "utf-8");
54
+ return JSON.parse(content);
55
+ }
56
+
57
+ async function migrate() {
58
+ console.log("🔄 Starting migration to SQLite...\n");
59
+
60
+ const db = getDb();
61
+ console.log("✅ Database initialized\n");
62
+
63
+ const oldSpendData = await loadOldSpendData();
64
+ if (oldSpendData) {
65
+ console.log(`📦 Migrating ${oldSpendData.sessions.length} sessions...`);
66
+
67
+ let migrated = 0;
68
+ let skipped = 0;
69
+
70
+ for (const session of oldSpendData.sessions) {
71
+ try {
72
+ upsertSession({
73
+ session_id: session.id,
74
+ total_cost: session.cost,
75
+ cwd: session.cwd,
76
+ date: session.date,
77
+ duration_ms: session.duration_ms,
78
+ lines_added: session.lines_added,
79
+ lines_removed: session.lines_removed,
80
+ last_resets_at: session.last_resets_at ?? null,
81
+ cumulative_counted: 0,
82
+ });
83
+ migrated++;
84
+ } catch (error) {
85
+ console.error(` ❌ Failed to migrate session ${session.id}:`, error);
86
+ skipped++;
87
+ }
88
+ }
89
+
90
+ console.log(` ✅ Migrated: ${migrated}`);
91
+ console.log(` ⚠️ Skipped: ${skipped}\n`);
92
+ }
93
+
94
+ const oldDailyUsage = await loadOldDailyUsage();
95
+ if (oldDailyUsage.length > 0) {
96
+ console.log(`📅 Migrating ${oldDailyUsage.length} period entries...`);
97
+
98
+ let migrated = 0;
99
+
100
+ for (const entry of oldDailyUsage) {
101
+ try {
102
+ upsertPeriod({
103
+ period_id: entry.resets_at,
104
+ total_cost: 0,
105
+ utilization: entry.utilization,
106
+ date: entry.date,
107
+ });
108
+ migrated++;
109
+ } catch (error) {
110
+ console.error(
111
+ ` ❌ Failed to migrate period ${entry.resets_at}:`,
112
+ error,
113
+ );
114
+ }
115
+ }
116
+
117
+ console.log(` ✅ Migrated: ${migrated}\n`);
118
+ }
119
+
120
+ const sessionCount = db
121
+ .query<{ count: number }, []>("SELECT COUNT(*) as count FROM sessions")
122
+ .get();
123
+ const periodCount = db
124
+ .query<{ count: number }, []>("SELECT COUNT(*) as count FROM periods")
125
+ .get();
126
+
127
+ console.log("📊 Migration Summary:");
128
+ console.log(` Sessions in DB: ${sessionCount?.count ?? 0}`);
129
+ console.log(` Periods in DB: ${periodCount?.count ?? 0}`);
130
+ console.log("\n✅ Migration complete!");
131
+ console.log("\n⚠️ Note: Period costs have been reset to 0.");
132
+ console.log(" They will be recalculated correctly as you use Claude Code.");
133
+ console.log(" The old JSON files are preserved as backup.");
134
+ }
135
+
136
+ migrate().catch(console.error);
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { table } from "table";
4
+ import { formatCost, formatDuration } from "../../../formatters";
5
+ import { getSessionsByDate } from "../database";
6
+
7
+ function parseDate(input?: string): string {
8
+ if (!input) {
9
+ return new Date().toISOString().split("T")[0];
10
+ }
11
+
12
+ // Try parsing as YYYY-MM-DD
13
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
14
+ return input;
15
+ }
16
+
17
+ // Try parsing as MM-DD (current year)
18
+ if (/^\d{2}-\d{2}$/.test(input)) {
19
+ const year = new Date().getFullYear();
20
+ return `${year}-${input}`;
21
+ }
22
+
23
+ // Try parsing as natural date
24
+ const parsed = new Date(input);
25
+ if (!Number.isNaN(parsed.getTime())) {
26
+ return parsed.toISOString().split("T")[0];
27
+ }
28
+
29
+ console.log(`⚠️ Invalid date format: "${input}". Using today.`);
30
+ return new Date().toISOString().split("T")[0];
31
+ }
32
+
33
+ function isToday(date: string): boolean {
34
+ return date === new Date().toISOString().split("T")[0];
35
+ }
36
+
37
+ async function main() {
38
+ const dateArg = process.env.DATE || process.argv[2];
39
+ const targetDate = parseDate(dateArg);
40
+ const sessions = getSessionsByDate(targetDate);
41
+
42
+ const dateLabel = isToday(targetDate) ? "Today" : targetDate;
43
+
44
+ if (sessions.length === 0) {
45
+ console.log(`📊 No sessions for ${dateLabel}`);
46
+ return;
47
+ }
48
+
49
+ const totalCost = sessions.reduce((sum, s) => sum + s.total_cost, 0);
50
+ const totalDuration = sessions.reduce((sum, s) => sum + s.duration_ms, 0);
51
+
52
+ console.log(`\n📊 Spend for ${dateLabel}\n`);
53
+ console.log(`Sessions: ${sessions.length}`);
54
+ console.log(`Total Cost: $${formatCost(totalCost)}`);
55
+ console.log(`Total Duration: ${formatDuration(totalDuration)}`);
56
+ console.log("\n📝 Sessions:\n");
57
+
58
+ const filteredSessions = sessions
59
+ .filter((session) => session.total_cost >= 0.1)
60
+ .sort((a, b) => b.total_cost - a.total_cost);
61
+
62
+ const tableData = [
63
+ ["ID", "Cost", "Duration", "Changes", "Directory"],
64
+ ...filteredSessions.map((session) => [
65
+ session.session_id.slice(0, 8),
66
+ `$${formatCost(session.total_cost)}`,
67
+ formatDuration(session.duration_ms),
68
+ `+${session.lines_added} -${session.lines_removed}`,
69
+ session.cwd.replace(
70
+ /^(?:\/Users\/[^/]+\/|[A-Z]:\\Users\\[^\\]+\\)/,
71
+ "~/",
72
+ ),
73
+ ]),
74
+ ];
75
+
76
+ console.log(table(tableData));
77
+ }
78
+
79
+ main();