aiblueprint-cli 1.4.80 → 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.
- package/README.md +21 -17
- package/agents-config/claude-config/scripts/statusline/data/.gitignore +8 -0
- package/agents-config/claude-config/scripts/statusline/data/.gitkeep +0 -0
- package/agents-config/claude-config/scripts/statusline/defaults.json +10 -10
- package/agents-config/claude-config/scripts/statusline/src/commands/interactive-config.ts +118 -6
- package/agents-config/claude-config/scripts/statusline/src/index.ts +100 -9
- package/agents-config/claude-config/scripts/statusline/src/lib/config.ts +28 -2
- package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/commands/weekly-analysis.ts +108 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/index.ts +164 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/limits/types.ts +15 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/migrate-to-sqlite.ts +136 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-day.ts +79 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-month.ts +66 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-project.ts +85 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/database.ts +395 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/index.ts +178 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/payload-logger.ts +163 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/types.ts +37 -0
- package/agents-config/claude-config/scripts/statusline/src/lib/presets.ts +13 -13
- package/agents-config/claude-config/scripts/statusline/statusline.config.free.json +4 -1
- package/agents-config/claude-config/scripts/statusline/statusline.config.json +44 -23
- package/agents-config/scripts/statusline/data/.gitignore +8 -0
- package/agents-config/scripts/statusline/data/.gitkeep +0 -0
- package/agents-config/scripts/statusline/defaults.json +10 -10
- package/agents-config/scripts/statusline/src/commands/interactive-config.ts +118 -6
- package/agents-config/scripts/statusline/src/index.ts +100 -9
- package/agents-config/scripts/statusline/src/lib/config.ts +28 -2
- package/agents-config/scripts/statusline/src/lib/features/limits/commands/weekly-analysis.ts +108 -0
- package/agents-config/scripts/statusline/src/lib/features/limits/index.ts +164 -0
- package/agents-config/scripts/statusline/src/lib/features/limits/types.ts +15 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/commands/migrate-to-sqlite.ts +136 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-day.ts +79 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-month.ts +66 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/commands/spend-project.ts +85 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/database.ts +395 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/index.ts +178 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/payload-logger.ts +163 -0
- package/agents-config/scripts/statusline/src/lib/features/spend/types.ts +37 -0
- package/agents-config/scripts/statusline/src/lib/presets.ts +13 -13
- package/agents-config/scripts/statusline/statusline.config.free.json +4 -1
- package/agents-config/scripts/statusline/statusline.config.json +44 -23
- package/agents-config/skills/agents-managers/SKILL.md +1 -1
- package/agents-config/skills/agents-managers/references/writing-agent-prompts.md +1 -1
- package/agents-config/skills/appstore-connect/SKILL.md +104 -0
- package/agents-config/skills/appstore-connect/scripts/asc-api.mjs +88 -0
- package/agents-config/skills/appstore-connect-setup/SKILL.md +159 -0
- package/agents-config/{commands/prompts/create-vitejs-app.md → skills/create-vitejs-app/SKILL.md} +2 -1
- package/agents-config/skills/environments-manager/SKILL.md +6 -6
- package/agents-config/skills/environments-manager/examples/{claude/commands/dev.md → skills/dev/SKILL.md} +2 -1
- package/agents-config/skills/environments-manager/examples/{claude/commands/lint.md → skills/lint/SKILL.md} +2 -1
- package/agents-config/skills/environments-manager/examples/{claude/commands/test.md → skills/test/SKILL.md} +2 -1
- package/agents-config/skills/environments-manager/examples/{claude/commands/typecheck.md → skills/typecheck/SKILL.md} +2 -1
- package/agents-config/skills/environments-manager/references/claude.md +13 -9
- package/agents-config/skills/environments-manager/references/cursor.md +2 -2
- package/agents-config/skills/ios-testflight/SKILL.md +280 -0
- package/agents-config/{commands/prompts/nextjs-add-prisma-db.md → skills/nextjs-add-prisma-db/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/nextjs-setup-better-auth.md → skills/nextjs-setup-better-auth/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/nextjs-setup-project.md → skills/nextjs-setup-project/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/prompt.md → skills/prompt/SKILL.md} +5 -0
- package/agents-config/{commands/prompts/saas-challenge-idea.md → skills/saas-challenge-idea/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-architecture.md → skills/saas-create-architecture/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-headline.md → skills/saas-create-headline/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-landing-copywritting.md → skills/saas-create-landing-copywritting/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-legals-docs.md → skills/saas-create-legals-docs/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-logos.md → skills/saas-create-logos/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-create-prd.md → skills/saas-create-prd/SKILL.md} +4 -3
- package/agents-config/{commands/prompts/saas-create-tasks.md → skills/saas-create-tasks/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-define-pricing.md → skills/saas-define-pricing/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-find-domain-name.md → skills/saas-find-domain-name/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/saas-implement-landing-page.md → skills/saas-implement-landing-page/SKILL.md} +1 -0
- package/agents-config/{commands/prompts/setup-tmux.md → skills/setup-tmux/SKILL.md} +5 -0
- package/agents-config/{commands/prompts/tools.md → skills/tools/SKILL.md} +1 -0
- package/agents-config/skills/ultrathink/SKILL.md +1 -1
- package/agents-config/skills/use-style/SKILL.md +7 -4
- package/agents-config/skills/use-style/examples/luma.html +221 -0
- package/agents-config/skills/use-style/examples/testspirite.html +340 -0
- package/agents-config/skills/use-style/styles/ios-app.md +350 -0
- package/agents-config/skills/use-style/styles/luma.md +422 -0
- package/agents-config/skills/use-style/styles/new-york-times.md +1 -1
- package/agents-config/skills/use-style/styles/split-auth.md +2 -2
- package/agents-config/skills/use-style/styles/testspirite.md +397 -0
- package/dist/cli.js +17 -80
- package/package.json +1 -1
- package/agents-config/claude-config/scripts/.claude/commands/fix-on-my-computer.md +0 -87
|
@@ -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);
|
package/agents-config/claude-config/scripts/statusline/src/lib/features/spend/commands/spend-day.ts
ADDED
|
@@ -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();
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { table } from "table";
|
|
4
|
+
import { formatCost, formatDuration } from "../../../formatters";
|
|
5
|
+
import { getAllSessions } from "../database";
|
|
6
|
+
import { getMonthStart } from "../index";
|
|
7
|
+
import type { SessionRow } from "../types";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
const allSessions = getAllSessions();
|
|
11
|
+
const monthStart = getMonthStart();
|
|
12
|
+
const monthStartStr = monthStart.toISOString().split("T")[0];
|
|
13
|
+
|
|
14
|
+
const monthSessions = allSessions.filter((s) => s.date >= monthStartStr);
|
|
15
|
+
|
|
16
|
+
if (monthSessions.length === 0) {
|
|
17
|
+
console.log("📊 No sessions this month");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const totalCost = monthSessions.reduce((sum, s) => sum + s.total_cost, 0);
|
|
22
|
+
const totalDuration = monthSessions.reduce(
|
|
23
|
+
(sum, s) => sum + s.duration_ms,
|
|
24
|
+
0,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const sessionsByDate = monthSessions.reduce(
|
|
28
|
+
(acc, session) => {
|
|
29
|
+
if (!acc[session.date]) {
|
|
30
|
+
acc[session.date] = [];
|
|
31
|
+
}
|
|
32
|
+
acc[session.date].push(session);
|
|
33
|
+
return acc;
|
|
34
|
+
},
|
|
35
|
+
{} as Record<string, SessionRow[]>,
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const monthName = monthStart.toLocaleString("default", { month: "long" });
|
|
39
|
+
|
|
40
|
+
console.log(`\n📊 ${monthName}'s Spend\n`);
|
|
41
|
+
console.log(`Sessions: ${monthSessions.length}`);
|
|
42
|
+
console.log(`Total Cost: $${formatCost(totalCost)}`);
|
|
43
|
+
console.log(`Total Duration: ${formatDuration(totalDuration)}`);
|
|
44
|
+
console.log("\n📅 By Date:\n");
|
|
45
|
+
|
|
46
|
+
const sortedDates = Object.keys(sessionsByDate).sort();
|
|
47
|
+
|
|
48
|
+
const tableData = [
|
|
49
|
+
["Date", "Cost", "Duration", "Sessions"],
|
|
50
|
+
...sortedDates.map((date) => {
|
|
51
|
+
const sessions = sessionsByDate[date];
|
|
52
|
+
const dayCost = sessions.reduce((sum, s) => sum + s.total_cost, 0);
|
|
53
|
+
const dayDuration = sessions.reduce((sum, s) => sum + s.duration_ms, 0);
|
|
54
|
+
return [
|
|
55
|
+
date,
|
|
56
|
+
`$${formatCost(dayCost)}`,
|
|
57
|
+
formatDuration(dayDuration),
|
|
58
|
+
sessions.length.toString(),
|
|
59
|
+
];
|
|
60
|
+
}),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
console.log(table(tableData));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
main();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { table } from "table";
|
|
4
|
+
import { formatCost, formatDuration } from "../../../formatters";
|
|
5
|
+
import { getAllSessions } from "../database";
|
|
6
|
+
|
|
7
|
+
function main() {
|
|
8
|
+
const projectName = process.argv[2];
|
|
9
|
+
|
|
10
|
+
if (!projectName) {
|
|
11
|
+
console.log("Usage: bun run spend:project <project-name>");
|
|
12
|
+
console.log('Example: bun run spend:project "statusline"');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const allSessions = getAllSessions();
|
|
17
|
+
const matchingSessions = allSessions.filter((session) =>
|
|
18
|
+
session.cwd.endsWith(projectName),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (matchingSessions.length === 0) {
|
|
22
|
+
console.log(`\nNo sessions found for project ending with "${projectName}"`);
|
|
23
|
+
console.log("\nAvailable project paths:");
|
|
24
|
+
const uniquePaths = [...new Set(allSessions.map((s) => s.cwd))];
|
|
25
|
+
for (const path of uniquePaths.slice(0, 10)) {
|
|
26
|
+
console.log(
|
|
27
|
+
` - ${path.replace(/^(?:\/Users\/[^/]+\/|[A-Z]:\\Users\\[^\\]+\\)/, "~/")}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const totalCost = matchingSessions.reduce((sum, s) => sum + s.total_cost, 0);
|
|
34
|
+
const totalDuration = matchingSessions.reduce(
|
|
35
|
+
(sum, s) => sum + s.duration_ms,
|
|
36
|
+
0,
|
|
37
|
+
);
|
|
38
|
+
const totalLinesAdded = matchingSessions.reduce(
|
|
39
|
+
(sum, s) => sum + s.lines_added,
|
|
40
|
+
0,
|
|
41
|
+
);
|
|
42
|
+
const totalLinesRemoved = matchingSessions.reduce(
|
|
43
|
+
(sum, s) => sum + s.lines_removed,
|
|
44
|
+
0,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
console.log(`\n📊 Spend for project: ${projectName}\n`);
|
|
48
|
+
console.log(`Sessions: ${matchingSessions.length}`);
|
|
49
|
+
console.log(`Total Cost: $${formatCost(totalCost)}`);
|
|
50
|
+
console.log(`Total Duration: ${formatDuration(totalDuration)}`);
|
|
51
|
+
console.log(`Total Changes: +${totalLinesAdded} -${totalLinesRemoved}`);
|
|
52
|
+
console.log("\n📝 Sessions by date:\n");
|
|
53
|
+
|
|
54
|
+
const sessionsByDate = matchingSessions.reduce(
|
|
55
|
+
(acc, session) => {
|
|
56
|
+
const date = session.date;
|
|
57
|
+
if (!acc[date]) {
|
|
58
|
+
acc[date] = { cost: 0, duration: 0, count: 0 };
|
|
59
|
+
}
|
|
60
|
+
acc[date].cost += session.total_cost;
|
|
61
|
+
acc[date].duration += session.duration_ms;
|
|
62
|
+
acc[date].count += 1;
|
|
63
|
+
return acc;
|
|
64
|
+
},
|
|
65
|
+
{} as Record<string, { cost: number; duration: number; count: number }>,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const sortedDates = Object.entries(sessionsByDate).sort(([a], [b]) =>
|
|
69
|
+
b.localeCompare(a),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const tableData = [
|
|
73
|
+
["Date", "Sessions", "Cost", "Duration"],
|
|
74
|
+
...sortedDates.map(([date, data]) => [
|
|
75
|
+
date,
|
|
76
|
+
String(data.count),
|
|
77
|
+
`$${formatCost(data.cost)}`,
|
|
78
|
+
formatDuration(data.duration),
|
|
79
|
+
]),
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
console.log(table(tableData));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
main();
|