git-fit 0.22.0 → 0.23.1
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.
- checksums.yaml +4 -4
- data/lib/git-fit.rb +2 -0
- data/lib/git_fit/cli/mcp.rb +24 -0
- data/lib/git_fit/cli.rb +3 -0
- data/lib/git_fit/export/ai.rb +28 -2
- data/lib/git_fit/llm/prompts.rb +3 -0
- data/lib/git_fit/mcp/tools.rb +118 -0
- data/lib/git_fit/mcp.rb +19 -0
- data/lib/git_fit/version.rb +1 -1
- metadata +18 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6d74822bf3f31986abcfc5c12266b5563a2f52b62b2f3495c4fc4a13c351540c
|
|
4
|
+
data.tar.gz: 423954310f4f01049fa29b24d40ca8e6b9bce856429446d22a84b91817bd0024
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c01b5a2fe18991ad86bddb9c2d52f529b69d4c047e168e63fa38cd184baedbbd72539b6be210cf2b21bc11bf0d4c4e27c996d7eb0437e2319a60bda60660999
|
|
7
|
+
data.tar.gz: ebcbb62b7e5ff968c6e814affb9fc733c441e850205ba99ec063df96c36e72b2418892229490d8a6d528e3f4d44f49284cd5f0be0659b21e930415069e336cfb
|
data/lib/git-fit.rb
CHANGED
|
@@ -90,7 +90,9 @@ require_relative 'git_fit/install/actions'
|
|
|
90
90
|
require_relative 'git_fit/cli/install_cli'
|
|
91
91
|
require_relative 'git_fit/cli/purge_cli'
|
|
92
92
|
require_relative 'git_fit/cli/gh_cli'
|
|
93
|
+
require_relative 'git_fit/mcp'
|
|
93
94
|
require_relative 'git_fit/cli/export'
|
|
95
|
+
require_relative 'git_fit/cli/mcp'
|
|
94
96
|
require_relative 'git_fit/cli/import_cli'
|
|
95
97
|
require_relative 'git_fit/cli/geo_cli'
|
|
96
98
|
require_relative 'git_fit/strava_web/file_check'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mcp'
|
|
4
|
+
|
|
5
|
+
module GitFit
|
|
6
|
+
class McpCLI < Thor
|
|
7
|
+
desc 'serve', 'Start the MCP stdio server (read-only workouts data for AI clients)'
|
|
8
|
+
|
|
9
|
+
no_commands do
|
|
10
|
+
def git_fit_config
|
|
11
|
+
@git_fit_config ||= GitFit::Config.new(options[:config])
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serve
|
|
16
|
+
config = git_fit_config
|
|
17
|
+
server = GitFit::MCP.build_server(config)
|
|
18
|
+
::MCP::Server::Transports::StdioTransport.new(server).open
|
|
19
|
+
rescue StandardError => e
|
|
20
|
+
say_status :error, "MCP server failed: #{e.message}", :red
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/git_fit/cli.rb
CHANGED
|
@@ -23,6 +23,9 @@ module GitFit
|
|
|
23
23
|
desc 'export SUBCOMMAND', 'Export activities to various formats'
|
|
24
24
|
subcommand 'export', ExportCLI
|
|
25
25
|
|
|
26
|
+
desc 'mcp serve', 'Start the MCP stdio server (read-only workouts data for AI clients)'
|
|
27
|
+
subcommand 'mcp', McpCLI
|
|
28
|
+
|
|
26
29
|
desc 'version', 'Show version'
|
|
27
30
|
def version
|
|
28
31
|
puts "git-fit v#{GitFit::VERSION}"
|
data/lib/git_fit/export/ai.rb
CHANGED
|
@@ -90,10 +90,34 @@ module GitFit
|
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# 报告周期取「最近完整期」:当最新数据落在进行中的当期,报告主体回退到
|
|
94
|
+
# 上一完整期(当期在上一期无数据时保留当期,如新用户/空库场景)。
|
|
95
|
+
# 当月的部分数据仍会出现在活动摘要中,模板要求模型仅作『最新动态』提及。
|
|
93
96
|
def period_label
|
|
94
97
|
latest = activity_rows.find { |r| r[:start_date_local] }
|
|
95
|
-
base = latest ? latest[:start_date_local].to_s : Time.now.utc.iso8601
|
|
96
|
-
|
|
98
|
+
base = latest ? latest[:start_date_local].to_s[0, period_width] : Time.now.utc.iso8601[0, period_width]
|
|
99
|
+
current = Time.now.strftime(period_format)
|
|
100
|
+
return base if base < current
|
|
101
|
+
|
|
102
|
+
previous = previous_period(current)
|
|
103
|
+
activity_rows.any? { |r| r[:start_date_local].to_s.start_with?(previous) } ? previous : base
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def period_width
|
|
107
|
+
@period == 'yearly' ? 4 : 7
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def period_format
|
|
111
|
+
@period == 'yearly' ? '%Y' : '%Y-%m'
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def previous_period(period)
|
|
115
|
+
if @period == 'yearly'
|
|
116
|
+
(period.to_i - 1).to_s
|
|
117
|
+
else
|
|
118
|
+
require 'date'
|
|
119
|
+
(Date.parse("#{period}-01") << 1).strftime('%Y-%m')
|
|
120
|
+
end
|
|
97
121
|
end
|
|
98
122
|
|
|
99
123
|
# Digest covers everything that changes the output: data, prompt mode,
|
|
@@ -104,6 +128,7 @@ module GitFit
|
|
|
104
128
|
'model' => @client.model,
|
|
105
129
|
'language' => @language,
|
|
106
130
|
'period_mode' => @period,
|
|
131
|
+
'period' => period_label,
|
|
107
132
|
'stats' => stats,
|
|
108
133
|
'activities' => lines,
|
|
109
134
|
)
|
|
@@ -185,6 +210,7 @@ module GitFit
|
|
|
185
210
|
def slots(stats, _rows)
|
|
186
211
|
{
|
|
187
212
|
'period' => period_label,
|
|
213
|
+
'today' => Time.now.strftime('%Y-%m-%d'),
|
|
188
214
|
'stats' => ::JSON.generate(stats),
|
|
189
215
|
'activities_summary' => activity_lines.join("\n"),
|
|
190
216
|
'language' => @language,
|
data/lib/git_fit/llm/prompts.rb
CHANGED
|
@@ -25,6 +25,7 @@ module GitFit
|
|
|
25
25
|
|
|
26
26
|
## 写作要求
|
|
27
27
|
- 汇总统计中的 summary 是全历史累计,不是 {{period}} 当期数据;当期数据以 monthly/yearly 中 {{period}} 对应行为准,活动摘要可用作补充
|
|
28
|
+
- 今天是 {{today}}。若活动摘要中出现 {{period}} 之后的当月数据,那是不完整的当月数据:仅在结尾以『最新动态』一句带过并明确标注,不纳入总结主体,禁止据此得出训练量骤降/中断等跨周期结论
|
|
28
29
|
- 用 {{language}} 撰写,Markdown 格式,正文 800 字以内
|
|
29
30
|
- 结构:先总后分 —— 总体负荷印象 → 亮点 → 隐忧
|
|
30
31
|
- 所有数字必须来自输入数据,禁止编造或外推
|
|
@@ -43,6 +44,7 @@ module GitFit
|
|
|
43
44
|
|
|
44
45
|
## 分析要求
|
|
45
46
|
- 汇总统计中的 summary 是全历史累计,不是 {{period}} 当期数据;对比当期与历史时以 monthly/yearly 分组行为准
|
|
47
|
+
- 今天是 {{today}}。当月不完整的数据(如月内仅数天)只可作为『最新动态』级别观察并明确标注,禁止据此输出骤降/中断等趋势性 anomaly
|
|
46
48
|
- 关注维度:趋势变化、个人纪录(PB)、疲劳信号、季节性规律、运动项目占比变化
|
|
47
49
|
- 输出 3-6 条,按重要性排序
|
|
48
50
|
- 数据不足以支撑的结论不要输出;无异常时给少量 info 条目即可,禁止强行制造发现
|
|
@@ -62,6 +64,7 @@ module GitFit
|
|
|
62
64
|
|
|
63
65
|
## 建议要求
|
|
64
66
|
- 汇总统计中的 summary 是全历史累计,不是 {{period}} 当期数据;当期负荷以 monthly/yearly 中 {{period}} 对应行为准
|
|
67
|
+
- 今天是 {{today}}。不完整的当月数据不足以支撑训练量调整类建议;如有,仅作观察性提示并标注
|
|
65
68
|
- 输出 2-4 条可执行建议,每条说明数据依据
|
|
66
69
|
- 除非数据明确支持(如连续多周稳定负荷且无疲劳信号),不建议提高训练量
|
|
67
70
|
- 关注一致性、恢复与渐进,而非单次表现
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'sequel'
|
|
5
|
+
require 'mcp'
|
|
6
|
+
|
|
7
|
+
module GitFit
|
|
8
|
+
module MCP
|
|
9
|
+
# 只读 MCP tools —— expose 本地 workouts DB 给 AI 客户端(Claude/Code/opencode 等)。
|
|
10
|
+
# 数据已公开(Pages 站点),无隐私增量;SQLite readonly 打开。
|
|
11
|
+
#
|
|
12
|
+
# 输出格式约定:query_activities 使用与 insights 摘要一致的紧凑行
|
|
13
|
+
# (run_id | 日期 类型 距离 时长 HR 爬升),供 get_activity 下钻。
|
|
14
|
+
module Tools
|
|
15
|
+
ACTIVITY_COLUMNS = %i[
|
|
16
|
+
run_id name distance moving_time elapsed_time sport_category sport_type
|
|
17
|
+
start_date start_date_local location_country summary_polyline
|
|
18
|
+
average_heartrate max_heartrate average_cadence max_cadence
|
|
19
|
+
average_power max_power average_temperature calories average_speed
|
|
20
|
+
elevation_gain elevation_loss elevation_min elevation_max steps source
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def db(config)
|
|
26
|
+
Sequel.sqlite(config.db_path, readonly: true)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def activity_line(row)
|
|
30
|
+
parts = [row[:run_id].to_s, row[:start_date_local].to_s[0, 10], row[:sport_category].to_s]
|
|
31
|
+
parts << format('%.1fkm', row[:distance] / 1000.0) if row[:distance]
|
|
32
|
+
parts << "#{(row[:moving_time] / 60).round}min" if row[:moving_time]
|
|
33
|
+
parts << "HR#{row[:average_heartrate].round}" if row[:average_heartrate]
|
|
34
|
+
parts << "asc#{row[:elevation_gain].round}m" if row[:elevation_gain]
|
|
35
|
+
parts.join(' | ')
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class QueryActivitiesTool < ::MCP::Tool
|
|
40
|
+
tool_name 'query_activities'
|
|
41
|
+
description '查询运动活动列表(按日期范围/运动类型过滤,返回最近的紧凑摘要行)。' \
|
|
42
|
+
'行格式:run_id | 日期 | 类型 | 距离 | 时长 | HR | 爬升。'
|
|
43
|
+
|
|
44
|
+
input_schema(
|
|
45
|
+
properties: {
|
|
46
|
+
from_date: { type: 'string', description: '起始日期 YYYY-MM-DD(含)', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
|
|
47
|
+
to_date: { type: 'string', description: '结束日期 YYYY-MM-DD(含)', pattern: '^\\d{4}-\\d{2}-\\d{2}$' },
|
|
48
|
+
sport: { type: 'string', description: '运动类型(ride/run/hike/walk/swim/workout/other)' },
|
|
49
|
+
limit: { type: 'integer', description: '返回条数上限(1-500)', default: 50 },
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
def call(server_context:, from_date: nil, to_date: nil, sport: nil, limit: 50)
|
|
55
|
+
db = GitFit::MCP::Tools.db(server_context[:config])
|
|
56
|
+
rows = db[:activities].order(Sequel.desc(:start_date_local)).limit(2000).all
|
|
57
|
+
rows = rows.select { |r| r[:start_date_local].to_s[0, 10] >= from_date } if from_date
|
|
58
|
+
rows = rows.select { |r| r[:start_date_local].to_s[0, 10] <= to_date } if to_date
|
|
59
|
+
rows = rows.select { |r| r[:sport_category] == sport } if sport && sport != 'all'
|
|
60
|
+
rows = rows.first(limit.to_i.clamp(1, 500))
|
|
61
|
+
lines = rows.map { |r| GitFit::MCP::Tools.activity_line(r) }
|
|
62
|
+
body = lines.empty? ? '无匹配活动' : "#{rows.size} 条匹配:\n#{lines.join("\n")}"
|
|
63
|
+
::MCP::Tool::Response.new([{ type: 'text', text: body }])
|
|
64
|
+
ensure
|
|
65
|
+
db&.disconnect
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class GetActivityTool < ::MCP::Tool
|
|
71
|
+
tool_name 'get_activity'
|
|
72
|
+
description '按 run_id 获取单个活动的全字段详情。run_id 来自 query_activities。'
|
|
73
|
+
|
|
74
|
+
input_schema(
|
|
75
|
+
properties: {
|
|
76
|
+
run_id: { type: 'string', description: '活动 ID(如 garmin_cn_12345678)' },
|
|
77
|
+
},
|
|
78
|
+
required: ['run_id'],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
class << self
|
|
82
|
+
def call(run_id:, server_context:)
|
|
83
|
+
db = GitFit::MCP::Tools.db(server_context[:config])
|
|
84
|
+
row = db[:activities].first(run_id: run_id)
|
|
85
|
+
unless row
|
|
86
|
+
return ::MCP::Tool::Response.new(
|
|
87
|
+
[{ type: 'text', text: "活动不存在:#{run_id}\n Fix: 先用 query_activities 查询可用 run_id" }],
|
|
88
|
+
error: true,
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
::MCP::Tool::Response.new([{ type: 'text', text: JSON.pretty_generate(
|
|
92
|
+
GitFit::MCP::Tools::ACTIVITY_COLUMNS.to_h { |k| [k, row[k]] },
|
|
93
|
+
) }])
|
|
94
|
+
ensure
|
|
95
|
+
db&.disconnect
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
class GetStatsTool < ::MCP::Tool
|
|
101
|
+
tool_name 'get_stats'
|
|
102
|
+
description '获取全量统计摘要:summary(全历史累计)/ monthly(按月)/ yearly(按年)/ streaks(连续天数)/ activity_types。' \
|
|
103
|
+
'注意 summary 是全历史累计,当期对比请看 monthly/yearly 分组行。'
|
|
104
|
+
|
|
105
|
+
input_schema(properties: {})
|
|
106
|
+
|
|
107
|
+
class << self
|
|
108
|
+
def call(server_context:)
|
|
109
|
+
db = GitFit::MCP::Tools.db(server_context[:config])
|
|
110
|
+
stats = GitFit::Export::Calculator.new(db: db).call
|
|
111
|
+
::MCP::Tool::Response.new([{ type: 'text', text: JSON.pretty_generate(stats) }])
|
|
112
|
+
ensure
|
|
113
|
+
db&.disconnect
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/git_fit/mcp.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'mcp/tools'
|
|
4
|
+
|
|
5
|
+
module GitFit
|
|
6
|
+
module MCP
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# 构建 MCP server(stdio transport 由 CLI 层启动)。
|
|
10
|
+
def build_server(config)
|
|
11
|
+
::MCP::Server.new(
|
|
12
|
+
name: 'git-fit-workouts',
|
|
13
|
+
version: GitFit::VERSION,
|
|
14
|
+
tools: [QueryActivitiesTool, GetActivityTool, GetStatsTool],
|
|
15
|
+
server_context: { config: config },
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/git_fit/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: git-fit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.23.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Lax
|
|
@@ -247,6 +247,20 @@ dependencies:
|
|
|
247
247
|
- - "~>"
|
|
248
248
|
- !ruby/object:Gem::Version
|
|
249
249
|
version: '1.0'
|
|
250
|
+
- !ruby/object:Gem::Dependency
|
|
251
|
+
name: mcp
|
|
252
|
+
requirement: !ruby/object:Gem::Requirement
|
|
253
|
+
requirements:
|
|
254
|
+
- - "~>"
|
|
255
|
+
- !ruby/object:Gem::Version
|
|
256
|
+
version: '1.4'
|
|
257
|
+
type: :runtime
|
|
258
|
+
prerelease: false
|
|
259
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
260
|
+
requirements:
|
|
261
|
+
- - "~>"
|
|
262
|
+
- !ruby/object:Gem::Version
|
|
263
|
+
version: '1.4'
|
|
250
264
|
description: A git extension CLI for aggregating, managing, and exporting fitness
|
|
251
265
|
activity data from multiple sources (Garmin, Strava, Keep, etc.)
|
|
252
266
|
email:
|
|
@@ -275,6 +289,7 @@ files:
|
|
|
275
289
|
- lib/git_fit/cli/gh_cli.rb
|
|
276
290
|
- lib/git_fit/cli/import_cli.rb
|
|
277
291
|
- lib/git_fit/cli/install_cli.rb
|
|
292
|
+
- lib/git_fit/cli/mcp.rb
|
|
278
293
|
- lib/git_fit/cli/purge_cli.rb
|
|
279
294
|
- lib/git_fit/cli/strava.rb
|
|
280
295
|
- lib/git_fit/cli/sync.rb
|
|
@@ -332,6 +347,8 @@ files:
|
|
|
332
347
|
- lib/git_fit/llm/errors.rb
|
|
333
348
|
- lib/git_fit/llm/prompts.rb
|
|
334
349
|
- lib/git_fit/llm/template.rb
|
|
350
|
+
- lib/git_fit/mcp.rb
|
|
351
|
+
- lib/git_fit/mcp/tools.rb
|
|
335
352
|
- lib/git_fit/parser.rb
|
|
336
353
|
- lib/git_fit/parser/base.rb
|
|
337
354
|
- lib/git_fit/parser/fit.rb
|