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
@@ -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();
@@ -0,0 +1,395 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { PeriodRow, SessionRow, TrackingRow } from "./types";
5
+
6
+ function getDbPath(): string {
7
+ const projectRoot = join(import.meta.dir, "..", "..", "..", "..");
8
+ const dataDir = join(projectRoot, "data");
9
+
10
+ if (!existsSync(dataDir)) {
11
+ mkdirSync(dataDir, { recursive: true });
12
+ }
13
+
14
+ return join(dataDir, "statusline.db");
15
+ }
16
+
17
+ let db: Database | null = null;
18
+
19
+ function runCumulativeCountedMigration(database: Database): void {
20
+ const sessions = database
21
+ .query<{ session_id: string }, []>("SELECT session_id FROM sessions")
22
+ .all();
23
+
24
+ for (const session of sessions) {
25
+ const result = database
26
+ .query<{ total: number }, [string]>(
27
+ "SELECT COALESCE(SUM(counted_cost), 0) as total FROM session_period_tracking WHERE session_id = ?",
28
+ )
29
+ .get(session.session_id);
30
+ const totalCounted = result?.total ?? 0;
31
+
32
+ database.run(
33
+ "UPDATE sessions SET cumulative_counted = ? WHERE session_id = ?",
34
+ [totalCounted, session.session_id],
35
+ );
36
+ }
37
+ }
38
+
39
+ export function getDb(): Database {
40
+ if (!db) {
41
+ db = new Database(getDbPath());
42
+ initializeSchema(db);
43
+ }
44
+ return db;
45
+ }
46
+
47
+ function migrateToCompositeKey(database: Database): void {
48
+ // Check if migration is needed by checking if session_id is the only PK
49
+ const tableInfo = database
50
+ .query<{ name: string; pk: number }, []>("PRAGMA table_info(sessions)")
51
+ .all();
52
+
53
+ const pkColumns = tableInfo.filter((col) => col.pk > 0);
54
+
55
+ // If we already have composite key (2 PK columns), skip migration
56
+ if (pkColumns.length >= 2) {
57
+ return;
58
+ }
59
+
60
+ // Check if sessions table exists
61
+ const tableExists = database
62
+ .query<{ name: string }, []>(
63
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'",
64
+ )
65
+ .get();
66
+
67
+ if (!tableExists) {
68
+ return; // Will be created fresh with new schema
69
+ }
70
+
71
+ // Create backup before migration
72
+ const dbPath = getDbPath();
73
+ const backupPath = `${dbPath}.backup-before-composite-key`;
74
+ if (!existsSync(backupPath)) {
75
+ copyFileSync(dbPath, backupPath);
76
+ }
77
+
78
+ database.run("BEGIN TRANSACTION");
79
+ try {
80
+ // 1. Create new table with composite key
81
+ database.run(`
82
+ CREATE TABLE sessions_new (
83
+ session_id TEXT NOT NULL,
84
+ date TEXT NOT NULL,
85
+ total_cost REAL NOT NULL DEFAULT 0,
86
+ cwd TEXT NOT NULL,
87
+ duration_ms INTEGER NOT NULL DEFAULT 0,
88
+ lines_added INTEGER NOT NULL DEFAULT 0,
89
+ lines_removed INTEGER NOT NULL DEFAULT 0,
90
+ last_resets_at TEXT,
91
+ cumulative_counted REAL NOT NULL DEFAULT 0,
92
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
93
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
94
+ PRIMARY KEY (session_id, date)
95
+ )
96
+ `);
97
+
98
+ // 2. Copy existing data
99
+ database.run(`
100
+ INSERT INTO sessions_new (session_id, date, total_cost, cwd, duration_ms,
101
+ lines_added, lines_removed, last_resets_at, cumulative_counted, created_at, updated_at)
102
+ SELECT session_id, date, total_cost, cwd, duration_ms,
103
+ lines_added, lines_removed, last_resets_at, cumulative_counted, created_at, updated_at
104
+ FROM sessions
105
+ `);
106
+
107
+ // 3. Drop old table
108
+ database.run("DROP TABLE sessions");
109
+
110
+ // 4. Rename new table
111
+ database.run("ALTER TABLE sessions_new RENAME TO sessions");
112
+
113
+ // 5. Recreate index
114
+ database.run(
115
+ "CREATE INDEX IF NOT EXISTS idx_sessions_date ON sessions(date)",
116
+ );
117
+ database.run(
118
+ "CREATE INDEX IF NOT EXISTS idx_sessions_session_id ON sessions(session_id)",
119
+ );
120
+
121
+ database.run("COMMIT");
122
+ } catch (e) {
123
+ database.run("ROLLBACK");
124
+ throw e;
125
+ }
126
+ }
127
+
128
+ function initializeSchema(database: Database): void {
129
+ database.run(`
130
+ CREATE TABLE IF NOT EXISTS sessions (
131
+ session_id TEXT NOT NULL,
132
+ date TEXT NOT NULL,
133
+ total_cost REAL NOT NULL DEFAULT 0,
134
+ cwd TEXT NOT NULL,
135
+ duration_ms INTEGER NOT NULL DEFAULT 0,
136
+ lines_added INTEGER NOT NULL DEFAULT 0,
137
+ lines_removed INTEGER NOT NULL DEFAULT 0,
138
+ last_resets_at TEXT,
139
+ cumulative_counted REAL NOT NULL DEFAULT 0,
140
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
141
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
142
+ PRIMARY KEY (session_id, date)
143
+ )
144
+ `);
145
+
146
+ // Run migration for existing databases with old schema
147
+ migrateToCompositeKey(database);
148
+
149
+ // Migration: add cumulative_counted column if it doesn't exist
150
+ try {
151
+ database.run(
152
+ `ALTER TABLE sessions ADD COLUMN cumulative_counted REAL NOT NULL DEFAULT 0`,
153
+ );
154
+ // Column was just added, run migration to populate it
155
+ runCumulativeCountedMigration(database);
156
+ } catch {
157
+ // Column already exists
158
+ }
159
+
160
+ database.run(`
161
+ CREATE TABLE IF NOT EXISTS session_period_tracking (
162
+ session_id TEXT NOT NULL,
163
+ period_id TEXT NOT NULL,
164
+ counted_cost REAL NOT NULL DEFAULT 0,
165
+ last_session_cost REAL NOT NULL DEFAULT 0,
166
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
167
+ PRIMARY KEY (session_id, period_id)
168
+ )
169
+ `);
170
+
171
+ database.run(`
172
+ CREATE TABLE IF NOT EXISTS periods (
173
+ period_id TEXT PRIMARY KEY,
174
+ total_cost REAL NOT NULL DEFAULT 0,
175
+ utilization INTEGER NOT NULL DEFAULT 0,
176
+ date TEXT NOT NULL,
177
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
178
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
179
+ )
180
+ `);
181
+
182
+ database.run(`
183
+ CREATE INDEX IF NOT EXISTS idx_sessions_date ON sessions(date)
184
+ `);
185
+
186
+ database.run(`
187
+ CREATE INDEX IF NOT EXISTS idx_tracking_period ON session_period_tracking(period_id)
188
+ `);
189
+
190
+ database.run(`
191
+ CREATE INDEX IF NOT EXISTS idx_periods_date ON periods(date)
192
+ `);
193
+
194
+ // New table: track max cumulative cost per project per day
195
+ // This handles /clear creating new session_ids with cumulative costs
196
+ database.run(`
197
+ CREATE TABLE IF NOT EXISTS project_cost_tracking (
198
+ cwd TEXT NOT NULL,
199
+ date TEXT NOT NULL,
200
+ max_cumulative_cost REAL NOT NULL DEFAULT 0,
201
+ updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
202
+ PRIMARY KEY (cwd, date)
203
+ )
204
+ `);
205
+ }
206
+
207
+ export function getSession(sessionId: string, date: string): SessionRow | null {
208
+ const database = getDb();
209
+ return database
210
+ .query<SessionRow, [string, string]>(
211
+ "SELECT * FROM sessions WHERE session_id = ? AND date = ?",
212
+ )
213
+ .get(sessionId, date);
214
+ }
215
+
216
+ export function upsertSession(session: SessionRow): void {
217
+ const database = getDb();
218
+ database.run(
219
+ `
220
+ INSERT INTO sessions (session_id, date, total_cost, cwd, duration_ms, lines_added, lines_removed, last_resets_at, cumulative_counted, updated_at)
221
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'))
222
+ ON CONFLICT(session_id, date) DO UPDATE SET
223
+ total_cost = excluded.total_cost,
224
+ cwd = excluded.cwd,
225
+ duration_ms = excluded.duration_ms,
226
+ lines_added = excluded.lines_added,
227
+ lines_removed = excluded.lines_removed,
228
+ last_resets_at = excluded.last_resets_at,
229
+ cumulative_counted = excluded.cumulative_counted,
230
+ updated_at = strftime('%s', 'now')
231
+ `,
232
+ [
233
+ session.session_id,
234
+ session.date,
235
+ session.total_cost,
236
+ session.cwd,
237
+ session.duration_ms,
238
+ session.lines_added,
239
+ session.lines_removed,
240
+ session.last_resets_at,
241
+ session.cumulative_counted,
242
+ ],
243
+ );
244
+ }
245
+
246
+ export function getTracking(
247
+ sessionId: string,
248
+ periodId: string,
249
+ ): TrackingRow | null {
250
+ const database = getDb();
251
+ return database
252
+ .query<TrackingRow, [string, string]>(
253
+ "SELECT * FROM session_period_tracking WHERE session_id = ? AND period_id = ?",
254
+ )
255
+ .get(sessionId, periodId);
256
+ }
257
+
258
+ export function upsertTracking(tracking: TrackingRow): void {
259
+ const database = getDb();
260
+ database.run(
261
+ `
262
+ INSERT INTO session_period_tracking (session_id, period_id, counted_cost, last_session_cost, updated_at)
263
+ VALUES (?, ?, ?, ?, strftime('%s', 'now'))
264
+ ON CONFLICT(session_id, period_id) DO UPDATE SET
265
+ counted_cost = excluded.counted_cost,
266
+ last_session_cost = excluded.last_session_cost,
267
+ updated_at = strftime('%s', 'now')
268
+ `,
269
+ [
270
+ tracking.session_id,
271
+ tracking.period_id,
272
+ tracking.counted_cost,
273
+ tracking.last_session_cost,
274
+ ],
275
+ );
276
+ }
277
+
278
+ export function upsertPeriod(period: PeriodRow): void {
279
+ const database = getDb();
280
+ database.run(
281
+ `
282
+ INSERT INTO periods (period_id, total_cost, utilization, date, updated_at)
283
+ VALUES (?, ?, ?, ?, strftime('%s', 'now'))
284
+ ON CONFLICT(period_id) DO UPDATE SET
285
+ total_cost = excluded.total_cost,
286
+ utilization = excluded.utilization,
287
+ date = excluded.date,
288
+ updated_at = strftime('%s', 'now')
289
+ `,
290
+ [period.period_id, period.total_cost, period.utilization, period.date],
291
+ );
292
+ }
293
+
294
+ export function addToPeriodCost(periodId: string, delta: number): void {
295
+ const database = getDb();
296
+ const today = new Date().toISOString().split("T")[0];
297
+
298
+ database.run(
299
+ `
300
+ INSERT INTO periods (period_id, total_cost, utilization, date, updated_at)
301
+ VALUES (?, ?, 0, ?, strftime('%s', 'now'))
302
+ ON CONFLICT(period_id) DO UPDATE SET
303
+ total_cost = total_cost + ?,
304
+ updated_at = strftime('%s', 'now')
305
+ `,
306
+ [periodId, delta, today, delta],
307
+ );
308
+ }
309
+
310
+ export function getPeriodCost(periodId: string): number {
311
+ const database = getDb();
312
+ const result = database
313
+ .query<{ total: number }, [string]>(
314
+ `
315
+ SELECT COALESCE(SUM(MIN(spt.counted_cost, s.total_cost)), 0) as total
316
+ FROM session_period_tracking spt
317
+ JOIN sessions s ON s.session_id = spt.session_id
318
+ WHERE spt.period_id = ?
319
+ `,
320
+ )
321
+ .get(periodId);
322
+ return result?.total ?? 0;
323
+ }
324
+
325
+ export function getSessionsByDate(date: string): SessionRow[] {
326
+ const database = getDb();
327
+ return database
328
+ .query<SessionRow, [string]>("SELECT * FROM sessions WHERE date = ?")
329
+ .all(date);
330
+ }
331
+
332
+ export function getTodaySessionsTotal(): number {
333
+ const database = getDb();
334
+ const today = new Date().toISOString().split("T")[0];
335
+ const result = database
336
+ .query<{ total: number }, [string]>(
337
+ "SELECT COALESCE(SUM(total_cost), 0) as total FROM sessions WHERE date = ?",
338
+ )
339
+ .get(today);
340
+ return result?.total ?? 0;
341
+ }
342
+
343
+ export function getAllSessions(): SessionRow[] {
344
+ const database = getDb();
345
+ return database
346
+ .query<SessionRow, []>("SELECT * FROM sessions ORDER BY date DESC")
347
+ .all();
348
+ }
349
+
350
+ export function getSessionTotalCounted(sessionId: string): number {
351
+ const database = getDb();
352
+ // Sum cumulative_counted across all dates for this session
353
+ const result = database
354
+ .query<{ total: number }, [string]>(
355
+ "SELECT COALESCE(SUM(cumulative_counted), 0) as total FROM sessions WHERE session_id = ?",
356
+ )
357
+ .get(sessionId);
358
+ return result?.total ?? 0;
359
+ }
360
+
361
+ export function closeDb(): void {
362
+ if (db) {
363
+ db.close();
364
+ db = null;
365
+ }
366
+ }
367
+
368
+ // Project cost tracking - handles /clear cumulative cost issue
369
+ export function getProjectMaxCost(cwd: string, date: string): number {
370
+ const database = getDb();
371
+ const result = database
372
+ .query<{ max_cumulative_cost: number }, [string, string]>(
373
+ "SELECT max_cumulative_cost FROM project_cost_tracking WHERE cwd = ? AND date = ?",
374
+ )
375
+ .get(cwd, date);
376
+ return result?.max_cumulative_cost ?? 0;
377
+ }
378
+
379
+ export function updateProjectMaxCost(
380
+ cwd: string,
381
+ date: string,
382
+ newCost: number,
383
+ ): void {
384
+ const database = getDb();
385
+ database.run(
386
+ `
387
+ INSERT INTO project_cost_tracking (cwd, date, max_cumulative_cost, updated_at)
388
+ VALUES (?, ?, ?, strftime('%s', 'now'))
389
+ ON CONFLICT(cwd, date) DO UPDATE SET
390
+ max_cumulative_cost = MAX(max_cumulative_cost, excluded.max_cumulative_cost),
391
+ updated_at = strftime('%s', 'now')
392
+ `,
393
+ [cwd, date, newCost],
394
+ );
395
+ }