agentfit 0.1.1 → 0.1.3

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.
@@ -41,6 +41,7 @@ export type SessionAvgAggregateOutputType = {
41
41
  apiErrors: number | null
42
42
  rateLimitErrors: number | null
43
43
  userInterruptions: number | null
44
+ systemPromptEdits: number | null
44
45
  }
45
46
 
46
47
  export type SessionSumAggregateOutputType = {
@@ -58,6 +59,7 @@ export type SessionSumAggregateOutputType = {
58
59
  apiErrors: number | null
59
60
  rateLimitErrors: number | null
60
61
  userInterruptions: number | null
62
+ systemPromptEdits: number | null
61
63
  }
62
64
 
63
65
  export type SessionMinAggregateOutputType = {
@@ -86,6 +88,7 @@ export type SessionMinAggregateOutputType = {
86
88
  rateLimitErrors: number | null
87
89
  userInterruptions: number | null
88
90
  permissionModesJson: string | null
91
+ systemPromptEdits: number | null
89
92
  createdAt: Date | null
90
93
  }
91
94
 
@@ -115,6 +118,7 @@ export type SessionMaxAggregateOutputType = {
115
118
  rateLimitErrors: number | null
116
119
  userInterruptions: number | null
117
120
  permissionModesJson: string | null
121
+ systemPromptEdits: number | null
118
122
  createdAt: Date | null
119
123
  }
120
124
 
@@ -144,6 +148,7 @@ export type SessionCountAggregateOutputType = {
144
148
  rateLimitErrors: number
145
149
  userInterruptions: number
146
150
  permissionModesJson: number
151
+ systemPromptEdits: number
147
152
  createdAt: number
148
153
  _all: number
149
154
  }
@@ -164,6 +169,7 @@ export type SessionAvgAggregateInputType = {
164
169
  apiErrors?: true
165
170
  rateLimitErrors?: true
166
171
  userInterruptions?: true
172
+ systemPromptEdits?: true
167
173
  }
168
174
 
169
175
  export type SessionSumAggregateInputType = {
@@ -181,6 +187,7 @@ export type SessionSumAggregateInputType = {
181
187
  apiErrors?: true
182
188
  rateLimitErrors?: true
183
189
  userInterruptions?: true
190
+ systemPromptEdits?: true
184
191
  }
185
192
 
186
193
  export type SessionMinAggregateInputType = {
@@ -209,6 +216,7 @@ export type SessionMinAggregateInputType = {
209
216
  rateLimitErrors?: true
210
217
  userInterruptions?: true
211
218
  permissionModesJson?: true
219
+ systemPromptEdits?: true
212
220
  createdAt?: true
213
221
  }
214
222
 
@@ -238,6 +246,7 @@ export type SessionMaxAggregateInputType = {
238
246
  rateLimitErrors?: true
239
247
  userInterruptions?: true
240
248
  permissionModesJson?: true
249
+ systemPromptEdits?: true
241
250
  createdAt?: true
242
251
  }
243
252
 
@@ -267,6 +276,7 @@ export type SessionCountAggregateInputType = {
267
276
  rateLimitErrors?: true
268
277
  userInterruptions?: true
269
278
  permissionModesJson?: true
279
+ systemPromptEdits?: true
270
280
  createdAt?: true
271
281
  _all?: true
272
282
  }
@@ -383,6 +393,7 @@ export type SessionGroupByOutputType = {
383
393
  rateLimitErrors: number
384
394
  userInterruptions: number
385
395
  permissionModesJson: string
396
+ systemPromptEdits: number
386
397
  createdAt: Date
387
398
  _count: SessionCountAggregateOutputType | null
388
399
  _avg: SessionAvgAggregateOutputType | null
@@ -435,6 +446,7 @@ export type SessionWhereInput = {
435
446
  rateLimitErrors?: Prisma.IntFilter<"Session"> | number
436
447
  userInterruptions?: Prisma.IntFilter<"Session"> | number
437
448
  permissionModesJson?: Prisma.StringFilter<"Session"> | string
449
+ systemPromptEdits?: Prisma.IntFilter<"Session"> | number
438
450
  createdAt?: Prisma.DateTimeFilter<"Session"> | Date | string
439
451
  }
440
452
 
@@ -464,6 +476,7 @@ export type SessionOrderByWithRelationInput = {
464
476
  rateLimitErrors?: Prisma.SortOrder
465
477
  userInterruptions?: Prisma.SortOrder
466
478
  permissionModesJson?: Prisma.SortOrder
479
+ systemPromptEdits?: Prisma.SortOrder
467
480
  createdAt?: Prisma.SortOrder
468
481
  }
469
482
 
@@ -496,6 +509,7 @@ export type SessionWhereUniqueInput = Prisma.AtLeast<{
496
509
  rateLimitErrors?: Prisma.IntFilter<"Session"> | number
497
510
  userInterruptions?: Prisma.IntFilter<"Session"> | number
498
511
  permissionModesJson?: Prisma.StringFilter<"Session"> | string
512
+ systemPromptEdits?: Prisma.IntFilter<"Session"> | number
499
513
  createdAt?: Prisma.DateTimeFilter<"Session"> | Date | string
500
514
  }, "id" | "sessionId">
501
515
 
@@ -525,6 +539,7 @@ export type SessionOrderByWithAggregationInput = {
525
539
  rateLimitErrors?: Prisma.SortOrder
526
540
  userInterruptions?: Prisma.SortOrder
527
541
  permissionModesJson?: Prisma.SortOrder
542
+ systemPromptEdits?: Prisma.SortOrder
528
543
  createdAt?: Prisma.SortOrder
529
544
  _count?: Prisma.SessionCountOrderByAggregateInput
530
545
  _avg?: Prisma.SessionAvgOrderByAggregateInput
@@ -562,6 +577,7 @@ export type SessionScalarWhereWithAggregatesInput = {
562
577
  rateLimitErrors?: Prisma.IntWithAggregatesFilter<"Session"> | number
563
578
  userInterruptions?: Prisma.IntWithAggregatesFilter<"Session"> | number
564
579
  permissionModesJson?: Prisma.StringWithAggregatesFilter<"Session"> | string
580
+ systemPromptEdits?: Prisma.IntWithAggregatesFilter<"Session"> | number
565
581
  createdAt?: Prisma.DateTimeWithAggregatesFilter<"Session"> | Date | string
566
582
  }
567
583
 
@@ -591,6 +607,7 @@ export type SessionCreateInput = {
591
607
  rateLimitErrors?: number
592
608
  userInterruptions?: number
593
609
  permissionModesJson?: string
610
+ systemPromptEdits?: number
594
611
  createdAt?: Date | string
595
612
  }
596
613
 
@@ -620,6 +637,7 @@ export type SessionUncheckedCreateInput = {
620
637
  rateLimitErrors?: number
621
638
  userInterruptions?: number
622
639
  permissionModesJson?: string
640
+ systemPromptEdits?: number
623
641
  createdAt?: Date | string
624
642
  }
625
643
 
@@ -649,6 +667,7 @@ export type SessionUpdateInput = {
649
667
  rateLimitErrors?: Prisma.IntFieldUpdateOperationsInput | number
650
668
  userInterruptions?: Prisma.IntFieldUpdateOperationsInput | number
651
669
  permissionModesJson?: Prisma.StringFieldUpdateOperationsInput | string
670
+ systemPromptEdits?: Prisma.IntFieldUpdateOperationsInput | number
652
671
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
653
672
  }
654
673
 
@@ -678,6 +697,7 @@ export type SessionUncheckedUpdateInput = {
678
697
  rateLimitErrors?: Prisma.IntFieldUpdateOperationsInput | number
679
698
  userInterruptions?: Prisma.IntFieldUpdateOperationsInput | number
680
699
  permissionModesJson?: Prisma.StringFieldUpdateOperationsInput | string
700
+ systemPromptEdits?: Prisma.IntFieldUpdateOperationsInput | number
681
701
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
682
702
  }
683
703
 
@@ -707,6 +727,7 @@ export type SessionCreateManyInput = {
707
727
  rateLimitErrors?: number
708
728
  userInterruptions?: number
709
729
  permissionModesJson?: string
730
+ systemPromptEdits?: number
710
731
  createdAt?: Date | string
711
732
  }
712
733
 
@@ -736,6 +757,7 @@ export type SessionUpdateManyMutationInput = {
736
757
  rateLimitErrors?: Prisma.IntFieldUpdateOperationsInput | number
737
758
  userInterruptions?: Prisma.IntFieldUpdateOperationsInput | number
738
759
  permissionModesJson?: Prisma.StringFieldUpdateOperationsInput | string
760
+ systemPromptEdits?: Prisma.IntFieldUpdateOperationsInput | number
739
761
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
740
762
  }
741
763
 
@@ -765,6 +787,7 @@ export type SessionUncheckedUpdateManyInput = {
765
787
  rateLimitErrors?: Prisma.IntFieldUpdateOperationsInput | number
766
788
  userInterruptions?: Prisma.IntFieldUpdateOperationsInput | number
767
789
  permissionModesJson?: Prisma.StringFieldUpdateOperationsInput | string
790
+ systemPromptEdits?: Prisma.IntFieldUpdateOperationsInput | number
768
791
  createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
769
792
  }
770
793
 
@@ -794,6 +817,7 @@ export type SessionCountOrderByAggregateInput = {
794
817
  rateLimitErrors?: Prisma.SortOrder
795
818
  userInterruptions?: Prisma.SortOrder
796
819
  permissionModesJson?: Prisma.SortOrder
820
+ systemPromptEdits?: Prisma.SortOrder
797
821
  createdAt?: Prisma.SortOrder
798
822
  }
799
823
 
@@ -812,6 +836,7 @@ export type SessionAvgOrderByAggregateInput = {
812
836
  apiErrors?: Prisma.SortOrder
813
837
  rateLimitErrors?: Prisma.SortOrder
814
838
  userInterruptions?: Prisma.SortOrder
839
+ systemPromptEdits?: Prisma.SortOrder
815
840
  }
816
841
 
817
842
  export type SessionMaxOrderByAggregateInput = {
@@ -840,6 +865,7 @@ export type SessionMaxOrderByAggregateInput = {
840
865
  rateLimitErrors?: Prisma.SortOrder
841
866
  userInterruptions?: Prisma.SortOrder
842
867
  permissionModesJson?: Prisma.SortOrder
868
+ systemPromptEdits?: Prisma.SortOrder
843
869
  createdAt?: Prisma.SortOrder
844
870
  }
845
871
 
@@ -869,6 +895,7 @@ export type SessionMinOrderByAggregateInput = {
869
895
  rateLimitErrors?: Prisma.SortOrder
870
896
  userInterruptions?: Prisma.SortOrder
871
897
  permissionModesJson?: Prisma.SortOrder
898
+ systemPromptEdits?: Prisma.SortOrder
872
899
  createdAt?: Prisma.SortOrder
873
900
  }
874
901
 
@@ -887,6 +914,7 @@ export type SessionSumOrderByAggregateInput = {
887
914
  apiErrors?: Prisma.SortOrder
888
915
  rateLimitErrors?: Prisma.SortOrder
889
916
  userInterruptions?: Prisma.SortOrder
917
+ systemPromptEdits?: Prisma.SortOrder
890
918
  }
891
919
 
892
920
  export type StringFieldUpdateOperationsInput = {
@@ -941,6 +969,7 @@ export type SessionSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs
941
969
  rateLimitErrors?: boolean
942
970
  userInterruptions?: boolean
943
971
  permissionModesJson?: boolean
972
+ systemPromptEdits?: boolean
944
973
  createdAt?: boolean
945
974
  }, ExtArgs["result"]["session"]>
946
975
 
@@ -970,6 +999,7 @@ export type SessionSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Exten
970
999
  rateLimitErrors?: boolean
971
1000
  userInterruptions?: boolean
972
1001
  permissionModesJson?: boolean
1002
+ systemPromptEdits?: boolean
973
1003
  createdAt?: boolean
974
1004
  }, ExtArgs["result"]["session"]>
975
1005
 
@@ -999,6 +1029,7 @@ export type SessionSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Exten
999
1029
  rateLimitErrors?: boolean
1000
1030
  userInterruptions?: boolean
1001
1031
  permissionModesJson?: boolean
1032
+ systemPromptEdits?: boolean
1002
1033
  createdAt?: boolean
1003
1034
  }, ExtArgs["result"]["session"]>
1004
1035
 
@@ -1028,10 +1059,11 @@ export type SessionSelectScalar = {
1028
1059
  rateLimitErrors?: boolean
1029
1060
  userInterruptions?: boolean
1030
1061
  permissionModesJson?: boolean
1062
+ systemPromptEdits?: boolean
1031
1063
  createdAt?: boolean
1032
1064
  }
1033
1065
 
1034
- export type SessionOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "sessionId" | "project" | "projectPath" | "startTime" | "endTime" | "durationMinutes" | "userMessages" | "assistantMessages" | "totalMessages" | "inputTokens" | "outputTokens" | "cacheCreationTokens" | "cacheReadTokens" | "totalTokens" | "costUSD" | "model" | "toolCallsTotal" | "toolCallsJson" | "skillCallsJson" | "messageTimestamps" | "apiErrors" | "rateLimitErrors" | "userInterruptions" | "permissionModesJson" | "createdAt", ExtArgs["result"]["session"]>
1066
+ export type SessionOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "sessionId" | "project" | "projectPath" | "startTime" | "endTime" | "durationMinutes" | "userMessages" | "assistantMessages" | "totalMessages" | "inputTokens" | "outputTokens" | "cacheCreationTokens" | "cacheReadTokens" | "totalTokens" | "costUSD" | "model" | "toolCallsTotal" | "toolCallsJson" | "skillCallsJson" | "messageTimestamps" | "apiErrors" | "rateLimitErrors" | "userInterruptions" | "permissionModesJson" | "systemPromptEdits" | "createdAt", ExtArgs["result"]["session"]>
1035
1067
 
1036
1068
  export type $SessionPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
1037
1069
  name: "Session"
@@ -1062,6 +1094,7 @@ export type $SessionPayload<ExtArgs extends runtime.Types.Extensions.InternalArg
1062
1094
  rateLimitErrors: number
1063
1095
  userInterruptions: number
1064
1096
  permissionModesJson: string
1097
+ systemPromptEdits: number
1065
1098
  createdAt: Date
1066
1099
  }, ExtArgs["result"]["session"]>
1067
1100
  composites: {}
@@ -1511,6 +1544,7 @@ export interface SessionFieldRefs {
1511
1544
  readonly rateLimitErrors: Prisma.FieldRef<"Session", 'Int'>
1512
1545
  readonly userInterruptions: Prisma.FieldRef<"Session", 'Int'>
1513
1546
  readonly permissionModesJson: Prisma.FieldRef<"Session", 'String'>
1547
+ readonly systemPromptEdits: Prisma.FieldRef<"Session", 'Int'>
1514
1548
  readonly createdAt: Prisma.FieldRef<"Session", 'DateTime'>
1515
1549
  }
1516
1550
 
package/lib/coach.ts CHANGED
@@ -378,6 +378,45 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
378
378
  })
379
379
  }
380
380
 
381
+ // System prompt engineering (CLAUDE.md / agent.md maintenance)
382
+ const totalSPEdits = overview.totalSystemPromptEdits
383
+ if (sessions.length > 10) {
384
+ if (totalSPEdits === 0) {
385
+ insights.push({
386
+ id: 'no-system-prompt-updates',
387
+ title: 'No CLAUDE.md / agent.md updates detected',
388
+ description: 'Your system prompt files (CLAUDE.md for Claude Code, agent.md for Codex) are your most powerful context engineering tool — they\'re loaded into every turn and cached across interactions. Zero edits means you may be missing out on persistent project-level instructions.',
389
+ category: 'context',
390
+ severity: 'warning',
391
+ craft: 'context',
392
+ metric: '0 edits',
393
+ recommendation: 'Create or update your CLAUDE.md with: project architecture, coding conventions, common pitfalls, and behavioral rules ("always read before editing"). This context is cached and reused every turn — high-signal tokens that compound over time.',
394
+ })
395
+ } else if (totalSPEdits >= 5) {
396
+ insights.push({
397
+ id: 'active-system-prompt',
398
+ title: 'Actively maintaining your system prompt',
399
+ description: `${totalSPEdits} edits to CLAUDE.md/agent.md — you're iterating on your system prompt like a well-tuned configuration. Each improvement compounds across every future session, making the agent smarter about your project's specific context.`,
400
+ category: 'context',
401
+ severity: 'achievement',
402
+ craft: 'context',
403
+ metric: `${totalSPEdits} edits`,
404
+ })
405
+ } else {
406
+ const editsPerSession = totalSPEdits / sessions.length
407
+ insights.push({
408
+ id: 'some-system-prompt',
409
+ title: 'Keep iterating on your system prompt',
410
+ description: `${totalSPEdits} edits to CLAUDE.md/agent.md across ${sessions.length} sessions. The best context engineers treat their system prompt as a living document — update it when you discover rules the agent should follow, files it should know about, or patterns it should prefer.`,
411
+ category: 'context',
412
+ severity: 'tip',
413
+ craft: 'context',
414
+ metric: `${totalSPEdits} edits`,
415
+ recommendation: 'After each session, ask yourself: "Did I repeat any instruction that should be in CLAUDE.md?" Add project rules, schemas, and conventions. High-signal stable context that gets cached = free quality improvement on every turn.',
416
+ })
417
+ }
418
+ }
419
+
381
420
  // Session naming / organization
382
421
  const avgSessionTokens = overview.totalTokens / Math.max(sessions.length, 1)
383
422
  if (avgSessionTokens > 150000 && longSessions.length <= 5) {
@@ -626,8 +665,8 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
626
665
  const clamp = (v: number) => Math.max(0, Math.min(100, Math.round(v)))
627
666
 
628
667
  // C — Context Engineering: holistic curation of tokens available to the LLM
629
- // cache efficiency + overflow avoidance + session length + just-in-time retrieval
630
- // + structured note-taking + output density + sub-agent isolation
668
+ // system prompt maintenance + cache efficiency + overflow avoidance + session length
669
+ // + just-in-time retrieval + structured note-taking + output density + sub-agent isolation
631
670
  const totalCache = overview.totalCacheCreationTokens + overview.totalCacheReadTokens
632
671
  const cacheHitRate = totalCache > 0 ? overview.totalCacheReadTokens / totalCache : 0
633
672
  const shortSessionRate = 1 - (longSessions.length / Math.max(sessions.length, 1))
@@ -643,14 +682,16 @@ export function generateCoachInsights(data: UsageData): CoachSummary {
643
682
  const agentIsolationRate = totalToolCalls > 0
644
683
  ? Math.min((toolUsage['Agent'] || 0) / Math.max(sessions.length, 1) / 3, 1) // 3 agent calls/session = full marks
645
684
  : 0
685
+ const sysPromptScore = Math.min(overview.totalSystemPromptEdits / 5, 1) // 5+ edits = full marks
646
686
  const contextScore = clamp(
647
- (cacheHitRate * 20) + // stable context reuse
648
- ((1 - Math.min(overflowRate, 1)) * 20) + // context window management
687
+ (sysPromptScore * 15) + // system prompt engineering (CLAUDE.md/agent.md)
688
+ (cacheHitRate * 15) + // stable context reuse
689
+ ((1 - Math.min(overflowRate, 1)) * 15) + // context window management
649
690
  (shortSessionRate * 10) + // session length discipline
650
691
  (Math.min(jitRetrievalRate / 0.4, 1) * 20) + // just-in-time retrieval
651
692
  (Math.min(noteTakingRate / 2, 1) * 10) + // structured note-taking
652
693
  (outputDensity * 10) + // output token density
653
- (agentIsolationRate * 10) // sub-agent context isolation
694
+ (agentIsolationRate * 5) // sub-agent context isolation
654
695
  )
655
696
 
656
697
  // R — Reach: tool diversity + subagent usage + skill adoption
package/lib/db.ts CHANGED
@@ -3,8 +3,8 @@ import { PrismaLibSql } from '@prisma/adapter-libsql'
3
3
  import path from 'path'
4
4
 
5
5
  function createPrisma() {
6
- const dbPath = path.resolve(process.cwd(), 'agentfit.db')
7
- const adapter = new PrismaLibSql({ url: `file:${dbPath}` })
6
+ const dbUrl = process.env.DATABASE_URL || `file:${path.resolve(process.cwd(), 'agentfit.db')}`
7
+ const adapter = new PrismaLibSql({ url: dbUrl })
8
8
  return new PrismaClient({ adapter })
9
9
  }
10
10
 
@@ -162,6 +162,7 @@ export function parseCodexSession(filePath: string): SessionSummary | null {
162
162
  apiErrors: 0,
163
163
  rateLimitErrors: 0,
164
164
  userInterruptions: 0,
165
+ systemPromptEdits: 0,
165
166
  permissionModes: {},
166
167
  }
167
168
  } catch {
package/lib/parse-logs.ts CHANGED
@@ -33,6 +33,7 @@ export interface SessionSummary {
33
33
  rateLimitErrors: number
34
34
  userInterruptions: number
35
35
  permissionModes: Record<string, number> // default, acceptEdits, bypassPermissions, plan
36
+ systemPromptEdits: number // edits/writes to CLAUDE.md, AGENTS.md, agent.md
36
37
  }
37
38
 
38
39
  export interface ProjectSummary {
@@ -81,6 +82,7 @@ export interface OverviewStats {
81
82
  totalApiErrors: number
82
83
  totalRateLimitDays: number
83
84
  totalUserInterruptions: number
85
+ totalSystemPromptEdits: number
84
86
  models: Record<string, number>
85
87
  skillUsage: Record<string, number>
86
88
  permissionModes: Record<string, number>
@@ -158,6 +160,7 @@ function parseSessionFile(
158
160
  let endTime = ''
159
161
  const toolCalls: Record<string, number> = {}
160
162
  const permissionModes: Record<string, number> = {}
163
+ let systemPromptEdits = 0
161
164
 
162
165
  for (const line of lines) {
163
166
  if (!line.trim()) continue
@@ -212,9 +215,17 @@ function parseSessionFile(
212
215
  'type' in block &&
213
216
  (block as Record<string, unknown>).type === 'tool_use'
214
217
  ) {
215
- const toolName = (block as Record<string, unknown>).name as string
218
+ const b = block as Record<string, unknown>
219
+ const toolName = b.name as string
216
220
  if (toolName) {
217
221
  toolCalls[toolName] = (toolCalls[toolName] || 0) + 1
222
+ // Detect system prompt file edits (CLAUDE.md, AGENTS.md, agent.md)
223
+ if ((toolName === 'Edit' || toolName === 'Write') && b.input && typeof b.input === 'object') {
224
+ const filePath = (b.input as Record<string, unknown>).file_path as string
225
+ if (filePath && /\/(CLAUDE|AGENTS|agent)\.md$/i.test(filePath)) {
226
+ systemPromptEdits++
227
+ }
228
+ }
218
229
  }
219
230
  }
220
231
  }
@@ -255,6 +266,7 @@ function parseSessionFile(
255
266
  rateLimitErrors: 0,
256
267
  userInterruptions: 0,
257
268
  permissionModes,
269
+ systemPromptEdits,
258
270
  }
259
271
  } catch {
260
272
  return null
@@ -395,6 +407,7 @@ export async function parseAllLogs(): Promise<UsageData> {
395
407
  totalApiErrors: 0,
396
408
  totalRateLimitDays: 0,
397
409
  totalUserInterruptions: 0,
410
+ totalSystemPromptEdits: sessions.reduce((a, s) => a + s.systemPromptEdits, 0),
398
411
  models,
399
412
  skillUsage: {},
400
413
  permissionModes: {},
@@ -422,6 +435,7 @@ function emptyUsageData(): UsageData {
422
435
  totalApiErrors: 0,
423
436
  totalRateLimitDays: 0,
424
437
  totalUserInterruptions: 0,
438
+ totalSystemPromptEdits: 0,
425
439
  models: {},
426
440
  skillUsage: {},
427
441
  permissionModes: {},
@@ -113,6 +113,7 @@ export function getCodexUsageData(): UsageData {
113
113
  totalApiErrors: 0,
114
114
  totalRateLimitDays: 0,
115
115
  totalUserInterruptions: 0,
116
+ totalSystemPromptEdits: 0,
116
117
  models,
117
118
  skillUsage: {},
118
119
  permissionModes: {},
@@ -140,6 +141,7 @@ function emptyUsageData(): UsageData {
140
141
  totalApiErrors: 0,
141
142
  totalRateLimitDays: 0,
142
143
  totalUserInterruptions: 0,
144
+ totalSystemPromptEdits: 0,
143
145
  models: {},
144
146
  skillUsage: {},
145
147
  permissionModes: {},
package/lib/queries.ts CHANGED
@@ -42,6 +42,7 @@ export async function getUsageData(): Promise<UsageData> {
42
42
  rateLimitErrors: s.rateLimitErrors,
43
43
  userInterruptions: s.userInterruptions,
44
44
  permissionModes: JSON.parse(s.permissionModesJson || '{}') as Record<string, number>,
45
+ systemPromptEdits: s.systemPromptEdits,
45
46
  }))
46
47
 
47
48
  // Aggregate projects
@@ -157,6 +158,7 @@ export async function getUsageData(): Promise<UsageData> {
157
158
  return days.size
158
159
  })(),
159
160
  totalUserInterruptions: sessions.reduce((a, s) => a + s.userInterruptions, 0),
161
+ totalSystemPromptEdits: sessions.reduce((a, s) => a + s.systemPromptEdits, 0),
160
162
  models,
161
163
  skillUsage,
162
164
  permissionModes,
@@ -184,6 +186,7 @@ function emptyUsageData(): UsageData {
184
186
  totalApiErrors: 0,
185
187
  totalRateLimitDays: 0,
186
188
  totalUserInterruptions: 0,
189
+ totalSystemPromptEdits: 0,
187
190
  models: {},
188
191
  skillUsage: {},
189
192
  permissionModes: {},
package/lib/sync.ts CHANGED
@@ -84,6 +84,7 @@ function parseSessionFile(
84
84
  let userInterruptions = 0
85
85
  const skillCalls: Record<string, number> = {}
86
86
  const permissionModes: Record<string, number> = {}
87
+ let systemPromptEdits = 0
87
88
 
88
89
  for (const line of lines) {
89
90
  if (!line.trim()) continue
@@ -177,6 +178,13 @@ function parseSessionFile(
177
178
  skillCalls[skillName] = (skillCalls[skillName] || 0) + 1
178
179
  }
179
180
  }
181
+ // Detect system prompt file edits (CLAUDE.md, AGENTS.md, agent.md)
182
+ if ((toolName === 'Edit' || toolName === 'Write') && b.input && typeof b.input === 'object') {
183
+ const fp = (b.input as Record<string, unknown>).file_path as string
184
+ if (fp && /\/(CLAUDE|AGENTS|agent)\.md$/i.test(fp)) {
185
+ systemPromptEdits++
186
+ }
187
+ }
180
188
  }
181
189
  }
182
190
 
@@ -243,6 +251,7 @@ function parseSessionFile(
243
251
  userInterruptions,
244
252
  skillCalls,
245
253
  permissionModes,
254
+ systemPromptEdits,
246
255
  images,
247
256
  }
248
257
  }
@@ -340,6 +349,7 @@ export async function syncLogs(): Promise<SyncResult> {
340
349
  rateLimitErrors: parsed.rateLimitErrors,
341
350
  userInterruptions: parsed.userInterruptions,
342
351
  permissionModesJson: JSON.stringify(parsed.permissionModes),
352
+ systemPromptEdits: parsed.systemPromptEdits,
343
353
  },
344
354
  })
345
355
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentfit",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Fitness tracker dashboard for AI coding agents (Claude Code, Codex). Visualize usage, cost, tokens, and productivity from local conversation logs.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "type": "git",
21
21
  "url": "https://github.com/harrywang/agentfit.git"
22
22
  },
23
- "main": "bin/agentfit.mjs",
23
+ "main": "electron/main.mjs",
24
24
  "scripts": {
25
25
  "postinstall": "prisma generate",
26
26
  "dev": "next dev --turbopack",
@@ -31,11 +31,12 @@
31
31
  "typecheck": "tsc --noEmit",
32
32
  "test": "vitest run",
33
33
  "test:watch": "vitest",
34
+ "prisma:schema-sql": "npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script | sed 's/CREATE TABLE/CREATE TABLE IF NOT EXISTS/g; s/CREATE UNIQUE INDEX/CREATE UNIQUE INDEX IF NOT EXISTS/g; s/CREATE INDEX/CREATE INDEX IF NOT EXISTS/g' > prisma/schema.sql",
34
35
  "electron:prepare": "node scripts/prepare-electron.mjs",
35
- "electron:dev": "npm run build && npm run electron:prepare && electron .",
36
+ "electron:dev": "npm run prisma:schema-sql && npm run build && npm run electron:prepare && electron .",
36
37
  "electron:build": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --config electron-builder.yml",
37
- "electron:build:mac": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --mac --config electron-builder.yml",
38
- "electron:build:win": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --win --config electron-builder.yml",
38
+ "electron:build:mac": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --mac --publish never --config electron-builder.yml",
39
+ "electron:build:win": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --win --publish never --config electron-builder.yml",
39
40
  "electron:build:all": "rm -rf dist-electron electron/server && npm run build && npm run electron:prepare && electron-builder --mac --win --config electron-builder.yml"
40
41
  },
41
42
  "dependencies": {
@@ -64,8 +65,6 @@
64
65
  "tw-animate-css": "^1.4.0"
65
66
  },
66
67
  "devDependencies": {
67
- "electron": "^35.1.2",
68
- "electron-builder": "^26.0.12",
69
68
  "@eslint/eslintrc": "^3",
70
69
  "@tailwindcss/postcss": "^4.2.1",
71
70
  "@testing-library/jest-dom": "^6.9.1",
@@ -74,6 +73,8 @@
74
73
  "@types/react": "^19.2.14",
75
74
  "@types/react-dom": "^19.2.3",
76
75
  "@vitejs/plugin-react": "^6.0.1",
76
+ "electron": "^35.1.2",
77
+ "electron-builder": "^26.0.12",
77
78
  "eslint": "^9.39.4",
78
79
  "eslint-config-next": "16.1.7",
79
80
  "jsdom": "^29.0.1",
@@ -0,0 +1,80 @@
1
+ -- CreateTable
2
+ CREATE TABLE "Session" (
3
+ "id" TEXT NOT NULL PRIMARY KEY,
4
+ "sessionId" TEXT NOT NULL,
5
+ "project" TEXT NOT NULL,
6
+ "projectPath" TEXT NOT NULL,
7
+ "startTime" DATETIME NOT NULL,
8
+ "endTime" DATETIME NOT NULL,
9
+ "durationMinutes" REAL NOT NULL,
10
+ "userMessages" INTEGER NOT NULL,
11
+ "assistantMessages" INTEGER NOT NULL,
12
+ "totalMessages" INTEGER NOT NULL,
13
+ "inputTokens" INTEGER NOT NULL,
14
+ "outputTokens" INTEGER NOT NULL,
15
+ "cacheCreationTokens" INTEGER NOT NULL,
16
+ "cacheReadTokens" INTEGER NOT NULL,
17
+ "totalTokens" INTEGER NOT NULL,
18
+ "costUSD" REAL NOT NULL,
19
+ "model" TEXT NOT NULL,
20
+ "toolCallsTotal" INTEGER NOT NULL,
21
+ "toolCallsJson" TEXT NOT NULL,
22
+ "skillCallsJson" TEXT NOT NULL DEFAULT '{}',
23
+ "messageTimestamps" TEXT NOT NULL DEFAULT '[]',
24
+ "apiErrors" INTEGER NOT NULL DEFAULT 0,
25
+ "rateLimitErrors" INTEGER NOT NULL DEFAULT 0,
26
+ "userInterruptions" INTEGER NOT NULL DEFAULT 0,
27
+ "permissionModesJson" TEXT NOT NULL DEFAULT '{}',
28
+ "systemPromptEdits" INTEGER NOT NULL DEFAULT 0,
29
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
30
+ );
31
+
32
+ -- CreateTable
33
+ CREATE TABLE "Image" (
34
+ "id" TEXT NOT NULL PRIMARY KEY,
35
+ "sessionId" TEXT NOT NULL,
36
+ "messageId" TEXT NOT NULL,
37
+ "filename" TEXT NOT NULL,
38
+ "mediaType" TEXT NOT NULL,
39
+ "sizeBytes" INTEGER NOT NULL,
40
+ "timestamp" DATETIME NOT NULL,
41
+ "role" TEXT NOT NULL,
42
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
43
+ );
44
+
45
+ -- CreateTable
46
+ CREATE TABLE "SyncLog" (
47
+ "id" TEXT NOT NULL PRIMARY KEY,
48
+ "syncedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
49
+ "filesProcessed" INTEGER NOT NULL,
50
+ "sessionsAdded" INTEGER NOT NULL,
51
+ "sessionsSkipped" INTEGER NOT NULL
52
+ );
53
+
54
+ -- CreateTable
55
+ CREATE TABLE "Report" (
56
+ "id" TEXT NOT NULL PRIMARY KEY,
57
+ "title" TEXT NOT NULL,
58
+ "generatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
59
+ "contentJson" TEXT NOT NULL,
60
+ "sessionCount" INTEGER NOT NULL,
61
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
62
+ );
63
+
64
+ -- CreateIndex
65
+ CREATE UNIQUE INDEX "Session_sessionId_key" ON "Session"("sessionId");
66
+
67
+ -- CreateIndex
68
+ CREATE INDEX "Session_project_idx" ON "Session"("project");
69
+
70
+ -- CreateIndex
71
+ CREATE INDEX "Session_startTime_idx" ON "Session"("startTime");
72
+
73
+ -- CreateIndex
74
+ CREATE INDEX "Image_sessionId_idx" ON "Image"("sessionId");
75
+
76
+ -- CreateIndex
77
+ CREATE UNIQUE INDEX "Image_sessionId_messageId_filename_key" ON "Image"("sessionId", "messageId", "filename");
78
+
79
+ -- CreateIndex
80
+ CREATE INDEX "Report_generatedAt_idx" ON "Report"("generatedAt");
@@ -0,0 +1,3 @@
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (e.g., Git)
3
+ provider = "sqlite"
@@ -33,6 +33,7 @@ model Session {
33
33
  rateLimitErrors Int @default(0)
34
34
  userInterruptions Int @default(0)
35
35
  permissionModesJson String @default("{}") // JSON: {default:N, acceptEdits:N, bypassPermissions:N, plan:N}
36
+ systemPromptEdits Int @default(0) // Edits/writes to CLAUDE.md, AGENTS.md, agent.md
36
37
  createdAt DateTime @default(now())
37
38
 
38
39
  @@index([project])