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.
- package/.github/workflows/release.yml +111 -0
- package/README.md +15 -8
- package/app/api/commands/route.ts +55 -1
- package/app/api/usage/route.ts +1 -0
- package/bin/agentfit.mjs +4 -5
- package/components/agent-coach.tsx +1 -1
- package/components/app-sidebar.tsx +12 -227
- package/components/daily-chart.tsx +47 -4
- package/components/dashboard-shell.tsx +22 -9
- package/components/data-provider.tsx +22 -6
- package/electron/entitlements.mac.plist +16 -0
- package/electron/init-db.mjs +37 -0
- package/electron/main.mjs +198 -0
- package/generated/prisma/internal/class.ts +4 -4
- package/generated/prisma/internal/prismaNamespace.ts +1 -0
- package/generated/prisma/internal/prismaNamespaceBrowser.ts +1 -0
- package/generated/prisma/models/Session.ts +35 -1
- package/lib/coach.ts +46 -5
- package/lib/db.ts +2 -2
- package/lib/parse-codex.ts +1 -0
- package/lib/parse-logs.ts +15 -1
- package/lib/queries-codex.ts +2 -0
- package/lib/queries.ts +3 -0
- package/lib/sync.ts +10 -0
- package/package.json +8 -7
- package/prisma/migrations/20260403214556_init/migration.sql +80 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +1 -0
- package/prisma/schema.sql +81 -0
- /package/app/(dashboard)/{settings → data-management}/page.tsx +0 -0
|
@@ -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
|
|
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
|
-
(
|
|
648
|
-
(
|
|
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 *
|
|
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
|
|
7
|
-
const adapter = new PrismaLibSql({ url:
|
|
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
|
|
package/lib/parse-codex.ts
CHANGED
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
|
|
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: {},
|
package/lib/queries-codex.ts
CHANGED
|
@@ -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.
|
|
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": "
|
|
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");
|
package/prisma/schema.prisma
CHANGED
|
@@ -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])
|