mcptask-rails-runner 0.2.4 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90fb6326f0a5b5ffde6055867b4709a7574acb7f1e8cc3facc9b00c37d60518e
4
- data.tar.gz: 30dd43c20ec169dadcb0729e63e6c074c84180a2649983478bb9c56a57b630fc
3
+ metadata.gz: '0519b4bebbc7206e258a1c0ca68d4b33a1d4ca65e7b1df96e94bc7fcea9d195a'
4
+ data.tar.gz: fb10b8db54c85034117828907847bb13c62d80f316591490724c2e8804fcebdd
5
5
  SHA512:
6
- metadata.gz: f2b2a21772d92034835ba3c75483f51312bda2ea235cc407fca2de43a44f4e5dca62f72f3cb4da04e6d50f0c22fa8bf258b02b3bfd0268378e9b984f8197c51c
7
- data.tar.gz: 901f117d861d84118d85e0a1684573858aa826f10a969df45130656b480452022618297f70fd35103e76d88f880525a4a787b0dabde04e7504ee1b0dd3d0fcd6
6
+ metadata.gz: 5d80a74fce1cb46bcef44e67e6d17c3015a9651a53fc8099b05fd75abb29c9f5b7f37e22d194f1192cfb6e1a7527ae9c5f3ae21b7462ce5bf43d01c64d49722e
7
+ data.tar.gz: 5fa67df8de13a98d9f42990dacfadea7b56b7cb040bbde6186cb9cb1d764ca46f9a191dbb163c4aade6bb4d036334249820af81e389801c7ba95e3279a2c9773
data/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ entries below describe wrapper changes only. Binary changes are listed in the
5
5
  [mcptask-releases](https://github.com/jchsoft/mcptask-releases/releases)
6
6
  release notes for the same tag.
7
7
 
8
+ ## 0.3.1
9
+
10
+ - `mcptask_runner:install` no longer asks for mcptask.online credentials on a
11
+ host whose token already resolves — from `.mcp.json` or the environment. It
12
+ says which one it found and where, and leaves it alone; set `MCPTASK_EMAIL`
13
+ and `MCPTASK_PASSWORD` to replace it deliberately. Where there really is no
14
+ token, the step now says whose account it wants, that the password is
15
+ exchanged once and never stored, which file the token lands in, and that the
16
+ runner will not start without one.
17
+ - The release workflow waits for the Go binaries instead of failing when the two
18
+ tags are pushed minutes apart.
19
+
20
+ ## 0.3.0
21
+
22
+ - The Go runner is the runner: this gem is the Rails distribution, and the
23
+ legacy `mcptask_runner` gem is retired to critical fixes and its role as the
24
+ conformance suite's reference implementation.
25
+
26
+ - The ARM Linux platform gem is named `aarch64-linux`, which is the platform
27
+ such hosts actually resolve. 0.2.4 published it as `arm64-linux`, which
28
+ matches nothing: every Graviton server, ARM CI runner and Docker container on
29
+ Apple Silicon fell back to the binary-less `ruby` gem and failed at run time.
30
+ That gem stays published and unreachable.
31
+ - `mcptask_runner:install` and `:update` copy the binary out of the gem to
32
+ `~/.mcptask/bin/mcptask_runner` and run it from there, so the scheduled job
33
+ they generate survives the next `bundle update` — which deletes the gem
34
+ directory the job used to point into. They print which version they wrote and
35
+ never replace a newer installed binary with an older one. Migrating hosts
36
+ should re-run install once, with `FORCE=1`, to regenerate the job.
37
+
8
38
  ## 0.2.4
9
39
 
10
40
  - First release. Thin Rails wrapper around the `mcptask_runner` Go binary,
data/README.md CHANGED
@@ -1,14 +1,33 @@
1
1
  # mcptask-rails-runner
2
2
 
3
3
  Rails distribution of the [mcptask.online](https://mcptask.online) autonomous
4
- runner. A thin Ruby wrapper around the `mcptask_runner` Go binary: the gem
5
- ships the platform-specific binary inside itself (`libexec/`) and exposes the
6
- same rake tasks as the legacy `mcptask_runner` gem, delegating 1:1 to the CLI.
4
+ runner, and the way a Rails app installs it. A thin Ruby wrapper around the
5
+ `mcptask_runner` Go binary: the gem ships the platform-specific binary inside
6
+ itself (`libexec/`) and exposes the same rake tasks as the legacy
7
+ `mcptask_runner` gem, delegating 1:1 to the CLI.
8
+
9
+ The legacy gem is retired. It gets critical fixes only, and it stays in
10
+ existence as the reference implementation the Go runner's conformance suite
11
+ measures against; everything new is written in Go. What you gain by moving:
12
+ Linux and Windows hosts, a scheduled job with no bundler in its path, and a
13
+ handful of places where the old implementation was simply wrong (its token
14
+ resolution read a different half of `.mcp.json` in each of three places, and
15
+ its stream reader died mid-run on an API error envelope).
7
16
 
8
17
  One gem version carries exactly one binary version — `mcptask-rails-runner
9
18
  X.Y.Z` contains the binary from the `vX.Y.Z` release of
10
19
  [jchsoft/mcptask-releases](https://github.com/jchsoft/mcptask-releases).
11
20
 
21
+ `mcptask_runner:install` and `:update` copy that binary out of the gem to
22
+ `~/.mcptask/bin/mcptask_runner` and run it from there. The scheduled job runs
23
+ that copy, because a job pointing inside a gem directory dies at the next
24
+ `bundle update` — at 08:00, in a log nobody reads. So the pin is per project:
25
+ the Gemfile decides what a foreground `rake mcptask_runner:*` runs, while the
26
+ machine has one installed binary however many projects it carries, and the
27
+ newest version any of them offers is the one that stays. Install and update
28
+ print which version they wrote, and never replace a newer installed binary
29
+ with an older one.
30
+
12
31
  ## Requirements
13
32
 
14
33
  - Ruby >= 3.0, Rails >= 6.0
@@ -36,8 +55,21 @@ The rake surface is identical — the migration is one Gemfile line:
36
55
  + gem "mcptask-rails-runner"
37
56
  ```
38
57
 
39
- Then `bundle install` and carry on; every `rake mcptask_runner:*` invocation,
40
- LaunchAgent and shell alias keeps working. Your existing
58
+ Then:
59
+
60
+ ```bash
61
+ bundle install
62
+ FORCE=1 bundle exec rake mcptask_runner:install
63
+ ```
64
+
65
+ Re-running install is what regenerates the scheduled job. The legacy launcher
66
+ runs `bundle exec rake` and logs the active version by grepping `Gemfile.lock`
67
+ for the old gem name — neither survives the rename, so a job left as it is
68
+ keeps working but stops recording what it ran. The regenerated one execs
69
+ `~/.mcptask/bin/mcptask_runner` directly, with no bundler in the 08:00 path.
70
+
71
+ Every `rake mcptask_runner:*` invocation and shell alias keeps working
72
+ unchanged. Your existing
41
73
  `config/mcptask_runner.yml`, `.mcp.json` and `.claude/` setup are read by the
42
74
  Go binary as-is. The only legacy task without a counterpart is
43
75
  `mcptask_runner:prepare:permissions` — permission sync is part of
@@ -47,8 +79,8 @@ Go binary as-is. The only legacy task without a counterpart is
47
79
 
48
80
  | Task | Delegates to |
49
81
  |---|---|
50
- | `mcptask_runner:install` | `mcptask_runner init` |
51
- | `mcptask_runner:update` | `mcptask_runner update` |
82
+ | `mcptask_runner:install` | `mcptask_runner init` (from `~/.mcptask/bin`) |
83
+ | `mcptask_runner:update` | `mcptask_runner update` (from `~/.mcptask/bin`) |
52
84
  | `mcptask_runner:bug_report` | `mcptask_runner bug-report` |
53
85
  | `mcptask_runner:version` | `mcptask_runner version` |
54
86
  | `mcptask_runner:manual:once` (`once_dry`, `today`, `daily`, `review`, `reviews`, `workflow`, `queue`) | `mcptask_runner run <mode>` |
@@ -61,7 +93,7 @@ the binary itself, exactly as before.
61
93
 
62
94
  ## Supported platforms
63
95
 
64
- Platform gems exist for `arm64-darwin`, `x86_64-darwin`, `arm64-linux`,
96
+ Platform gems exist for `arm64-darwin`, `x86_64-darwin`, `aarch64-linux`,
65
97
  `x86_64-linux` and `x64-mingw-ucrt`. On anything else bundler falls back to
66
98
  the binary-less `ruby` platform gem, which fails at run time with
67
99
  instructions: install the binary from
@@ -69,9 +101,11 @@ instructions: install the binary from
69
101
  `install.sh` / npm `@mcptask/cli`) and point `MCPTASK_RUNNER_BIN` at it. The
70
102
  same variable also serves offline or air-gapped installs.
71
103
 
72
- The wrapper always executes the binary through an absolute path inside the
73
- gem, never through PATH the binary shares its name with the legacy tooling,
74
- and a PATH lookup could pick up the wrong install.
104
+ The wrapper always executes the binary through an absolute path inside the
105
+ gem for the run tasks, `~/.mcptask/bin` for install and update and never
106
+ through PATH: the binary shares its name with the legacy tooling, and a PATH
107
+ lookup could pick up the wrong install. `MCPTASK_RUNNER_BIN` overrides both,
108
+ and install leaves `~/.mcptask/bin` untouched while it is set.
75
109
 
76
110
  ## Development
77
111
 
@@ -84,11 +118,23 @@ bundle exec rake package:all # build all platform gems into pkg/
84
118
 
85
119
  ## Releasing
86
120
 
87
- Releases are cut by tagging: bump `lib/mcptask_rails_runner/version.rb` to
88
- match an existing `mcptask-releases` tag, push a matching `vX.Y.Z` tag, and
89
- the Release workflow downloads the binaries (sha256-verified), builds the
90
- platform gems and publishes them to rubygems.org through OIDC trusted
91
- publishing. See `.github/workflows/release.yml`.
121
+ Releases are cut by tagging, twice the binaries come from the Go repository
122
+ and the gems from this one:
123
+
124
+ ```bash
125
+ # 1. bump lib/mcptask_rails_runner/version.rb, commit, push
126
+ # 2. in mcptask_go_runner:
127
+ git tag -a v0.3.0 -m v0.3.0 && git push origin v0.3.0
128
+ # 3. here, straight after — no waiting:
129
+ git tag -a v0.3.0 -m v0.3.0 && git push origin v0.3.0
130
+ ```
131
+
132
+ Step 3 needs no waiting because the Release workflow's fetch waits for the
133
+ binaries itself (`MCPTASK_WAIT_FOR_RELEASE`, 15 minutes against a Go release
134
+ that takes about four). It then verifies each archive against the release's
135
+ `checksums.txt`, builds the platform gems and publishes them to rubygems.org
136
+ through OIDC trusted publishing. A tag that disagrees with `version.rb` is
137
+ refused before anything is downloaded. See `.github/workflows/release.yml`.
92
138
 
93
139
  One-time setup on rubygems.org, before the first tag can publish anything —
94
140
  the gem name has to exist as a trusted-publisher entry:
@@ -1,31 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
4
+
3
5
  module McptaskRailsRunner
4
- # Locates and executes the bundled Go binary.
6
+ # Locates, installs and executes the bundled Go binary.
5
7
  module Binary
6
8
  GEM_ROOT = File.expand_path("../..", __dir__)
7
9
 
8
10
  SUPPORTED_PLATFORMS = %w[
9
11
  arm64-darwin
10
12
  x86_64-darwin
11
- arm64-linux
13
+ aarch64-linux
12
14
  x86_64-linux
13
15
  x64-mingw-ucrt
14
16
  ].freeze
15
17
 
18
+ # Where the binary is installed to live, outside every gem directory.
19
+ #
20
+ # The scheduled job runs this path, and it has to be one that survives a
21
+ # `bundle update`: a job pointing into
22
+ # gems/mcptask-rails-runner-<version>-<platform>/libexec dies the moment the
23
+ # gem is bumped, at 08:00, in a log nobody reads. Under the runner's own
24
+ # ~/.mcptask rather than /usr/local/bin, because `bundle install` has no
25
+ # sudo and a gem cannot write there. The Go side knows the same path — see
26
+ # InstalledBinary in internal/install/schedule.go.
27
+ INSTALL_DIR = File.join(Dir.home, ".mcptask", "bin")
28
+
29
+ # The line `mcptask_runner version` prints the version on.
30
+ VERSION_LINE = /Version:\s*(\S+)/
31
+
16
32
  module_function
17
33
 
18
34
  def executable_name
19
35
  Gem.win_platform? ? "mcptask_runner.exe" : "mcptask_runner"
20
36
  end
21
37
 
22
- # Absolute path to the bundled binary. Never resolved through PATH: the
23
- # binary shares the mcptask_runner name with the legacy gem's rake
38
+ # Absolute path to the binary this gem runs. Never resolved through PATH:
39
+ # the binary shares the mcptask_runner name with the legacy gem's rake
24
40
  # namespace, and a PATH lookup could pick up a stale or wrong install.
25
41
  def path(gem_root: GEM_ROOT)
26
42
  override = ENV["MCPTASK_RUNNER_BIN"]
27
43
  return override unless override.nil? || override.empty?
28
44
 
45
+ bundled_path(gem_root: gem_root)
46
+ end
47
+
48
+ # The copy that ships inside this gem, which is the one the Gemfile pins.
49
+ def bundled_path(gem_root: GEM_ROOT)
29
50
  exe = File.join(gem_root, "libexec", executable_name)
30
51
  return exe if File.file?(exe)
31
52
 
@@ -43,10 +64,91 @@ module McptaskRailsRunner
43
64
  MSG
44
65
  end
45
66
 
67
+ # The version-free path the installed copy lives at.
68
+ def installed_path(install_dir: INSTALL_DIR)
69
+ File.join(install_dir, executable_name)
70
+ end
71
+
72
+ # Copies the bundled binary out of the gem, and returns what to run.
73
+ #
74
+ # The gem bootstraps the binary; it does not host it. Everything the
75
+ # scheduled job touches has to outlive `bundle update`, and a gem directory
76
+ # does not — so install and update put a copy where nothing but they will
77
+ # ever move it. A copy rather than a symlink: a link into the gem's libexec
78
+ # dangles on the same bundle update, which is the identical 08:00 failure
79
+ # moved one step along, and Windows wants a privilege for symlinks besides.
80
+ #
81
+ # Teaching the launcher to run `bundle exec rake` instead would fix the path
82
+ # and put bundler and Gemfile.lock back into the 08:00 path — the layer the
83
+ # Go port set out to remove.
84
+ #
85
+ # Newest wins, and says so. A host with several projects has several gem
86
+ # versions and one installed binary, so a project pinning an older gem must
87
+ # not quietly drag the machine's runner backwards.
88
+ def install!(install_dir: INSTALL_DIR, gem_root: GEM_ROOT, out: $stdout)
89
+ override = ENV["MCPTASK_RUNNER_BIN"]
90
+ unless override.nil? || override.empty?
91
+ out.puts "[mcptask_runner] MCPTASK_RUNNER_BIN is set — running #{override} " \
92
+ "and leaving #{installed_path(install_dir: install_dir)} alone."
93
+ return override
94
+ end
95
+
96
+ source = bundled_path(gem_root: gem_root)
97
+ target = installed_path(install_dir: install_dir)
98
+
99
+ existing = installed_version(target)
100
+ if existing && existing > Gem::Version.new(VERSION)
101
+ out.puts "[mcptask_runner] #{target} already holds #{existing}, which is newer than this " \
102
+ "gem's #{VERSION} — keeping it. Delete that file to install this gem's binary instead."
103
+ return target
104
+ end
105
+
106
+ copy(source, target)
107
+ out.puts "[mcptask_runner] installed mcptask_runner #{VERSION} into #{target}" \
108
+ "#{existing ? " (replacing #{existing})" : ''}."
109
+ target
110
+ end
111
+
46
112
  # Replaces the current process, so the binary's exit code and signal
47
113
  # handling reach the caller (rake) unchanged.
48
114
  def exec!(*args)
49
115
  Kernel.exec(path, *args)
50
116
  end
117
+
118
+ # Installs the binary outside the gem first, then runs that copy.
119
+ #
120
+ # Used by install and update, the two tasks that generate the scheduled job:
121
+ # the job runs whichever binary is executing them, so it has to be the copy
122
+ # at the version-free path rather than the one inside this gem.
123
+ def exec_installed!(*args)
124
+ Kernel.exec(install!, *args)
125
+ end
126
+
127
+ # What the installed copy reports about itself, or nil when there is none —
128
+ # or when it cannot be asked, which a stray file at that path cannot be.
129
+ #
130
+ # Asked of the binary rather than recorded in a marker file beside it: the
131
+ # binary is what actually runs at 08:00, and a marker can only be right
132
+ # about installs that went through here.
133
+ def installed_version(target)
134
+ return nil unless File.file?(target)
135
+
136
+ banner = IO.popen([target, "version"], err: File::NULL, &:read)
137
+ raw = banner.to_s[VERSION_LINE, 1]
138
+ raw && Gem::Version.new(raw)
139
+ rescue StandardError
140
+ nil
141
+ end
142
+
143
+ # Written beside the target and renamed into place: a half-copied binary
144
+ # over a working one is a scheduled job that fails on a corrupt file, and
145
+ # the rename also sidesteps the ETXTBSY a running copy would give.
146
+ def copy(source, target)
147
+ FileUtils.mkdir_p(File.dirname(target))
148
+ staged = "#{target}.new"
149
+ FileUtils.cp(source, staged)
150
+ FileUtils.chmod(0o755, staged)
151
+ File.rename(staged, target)
152
+ end
51
153
  end
52
154
  end
@@ -1,7 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module McptaskRailsRunner
4
- # The gem version is pinned 1:1 to the mcptask_runner binary release it
5
- # carries (tag v#{VERSION} in jchsoft/mcptask-releases). Bump both together.
6
- VERSION = "0.2.4"
4
+ # The gem version names the mcptask_runner binary release it carries (tag
5
+ # v#{VERSION} in jchsoft/mcptask-releases). Bump both together.
6
+ #
7
+ # What it does not name is the binary the host runs at 08:00. That one is
8
+ # installed outside every gem directory, by install and update, and a machine
9
+ # has one of it however many projects and gem versions it carries — see
10
+ # Binary::INSTALL_DIR. So the pin is per project and per foreground rake
11
+ # invocation; the installed copy is per machine, and the newest binary any of
12
+ # those projects offers is the one that stays.
13
+ VERSION = "0.3.1"
7
14
  end
@@ -4,18 +4,25 @@
4
4
  # a customer is only a Gemfile line swap. Every task delegates 1:1 to the
5
5
  # bundled Go CLI via Kernel#exec — the binary's exit code and signal handling
6
6
  # reach the caller unchanged, and nothing runs after the delegation line.
7
+ #
8
+ # Two of them delegate to the installed copy instead of the bundled one. install
9
+ # and update are the tasks that generate the scheduled job, and the job runs
10
+ # whichever binary generated it: pointing it inside this gem would have it die
11
+ # at the next `bundle update`, which deletes that directory. Everything else
12
+ # runs the binary the Gemfile pins, which is what a foreground rake invocation
13
+ # should do.
7
14
 
8
15
  require "mcptask_rails_runner"
9
16
 
10
17
  namespace :mcptask_runner do
11
18
  desc "Install skills, permissions, token and scheduled job into this project"
12
19
  task :install do
13
- McptaskRailsRunner::Binary.exec!("init")
20
+ McptaskRailsRunner::Binary.exec_installed!("init")
14
21
  end
15
22
 
16
23
  desc "Refresh bundled skills and helpers"
17
24
  task :update do
18
- McptaskRailsRunner::Binary.exec!("update")
25
+ McptaskRailsRunner::Binary.exec_installed!("update")
19
26
  end
20
27
 
21
28
  desc "File a bug against the project with the last run log attached"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mcptask-rails-runner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josef Chmel