git-fit 0.22.0 → 0.23.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1f9fda747ee0bdc1b1d6588bce636576cb8e371ea0ae36b2bcfedd377a516891
4
- data.tar.gz: e19094889528196ead4715706a46fe035516d219ba2c69264cf32e4fc0820da6
3
+ metadata.gz: 05e915997e2a8843c5a47a964ff957134529c0712a2dd0743bd9f71828c87d3d
4
+ data.tar.gz: 96307ebf14dd9c1a37fa8615552d0daccc34218d6c2ff99ec6bfaad0621e5954
5
5
  SHA512:
6
- metadata.gz: a3fec0c7cfe0f51d7087698900585e480b9ce52d73aa6a8238cfa2493d5500e9d20e93087dda785da91844b3099ea1c4d36b4e642dbd867187d94843ae63b466
7
- data.tar.gz: b98ec2e23dc34c3c5694d4627c6ae12a153a2b185d76f0f9567903c2e39a4ff6bb7995ff928e4b17ef4978cdd0a03485e38ae41ef17d9f95bf63f16870d2324b
6
+ metadata.gz: 4b2e303432bab61591e5cc2835f3978574b21357f71bad88c37801c90614bc6f19f0f809cf7136c02b0d6e45d391aa6a76a1caeedd7666b2b69f5894d41f553f
7
+ data.tar.gz: 37090a85b6374f2ce0ee85b51c89b0bfc2ff6fbbf6d2a26c217ef4a16649480f78205b2d5abed165d3307763c464baae3c5d1f6e7144fc86aeb2a0c186e7751a
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}"
@@ -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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitFit
4
- VERSION = '0.22.0'
4
+ VERSION = '0.23.0'
5
5
  end
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.22.0
4
+ version: 0.23.0
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