yamine 0.14.1 → 0.14.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5f2bd591be3e0b58deef894b8772575e46068fe4cc6287eaedf18926874f965
4
- data.tar.gz: a1136bf238dc84bb2c150a00e02c385f19d3c9dd17ed94142091772712777768
3
+ metadata.gz: 8af42a840ce756689e6c37140a3a8cdf402c51aa71b9819b88df624d6a6ffaae
4
+ data.tar.gz: 0a51ab3de0e5605ea8b5152720f76957530722a1b65740dfebc0ab29d14337f9
5
5
  SHA512:
6
- metadata.gz: 65139ff9a320e9fd827ef4ee1ea2b2f43d3ddb5d8aa14e31a642085a2e74cfa8f95ecdd03d3d1bd12af567a971d1f124c3b80cb5baa835d582cf5633169e73ca
7
- data.tar.gz: 69b45bfe7fa0852be260ed74a1985a4f3052afa6aecd4bf821e29de721c71005082ad4b3ba078532df9ecf759f19a8ee9495f650734c296fc6e878b9356459ed
6
+ metadata.gz: e7823296acbadf8e6cc07079ff3f2d3ad56809d2de4973e6ed821124888cd2ee2144e267a754e585454fbc5c808c23fb9984e8da9880de4fda3b2b58bfcfff65
7
+ data.tar.gz: 2642187bbffd601efb5272f557577014704a4415b76c45eefbfa177d3e337b62074d5b60f36525c7a25b174684af7ccc5da31bbd8abaf59a9cdfa9fb864fae96
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.3] — 2026-09-15
4
+
5
+ ### Fixed
6
+
7
+ - **WebSocket upgrades carry the same forwarded headers as every other
8
+ request.** Upgrade requests (ActionCable, Turbo Streams over cable)
9
+ were piped to the backend byte-for-byte, so `X-Forwarded-Proto`,
10
+ `X-Forwarded-For`, and `X-Forwarded-Host` never arrived — the one
11
+ request class that skipped `set_forwarded`. An app behind the proxy
12
+ then saw a `wss://` connection as plain `http`, and Rails' ActionCable
13
+ origin check (Origin vs. scheme + Host) compared
14
+ `https://app.localhost` against `http://app.localhost` and refused the
15
+ socket with "Request origin not allowed". Upgrades now run the same
16
+ `set_forwarded` path as every other request before the head is
17
+ forwarded; the bytes after the header block still pipe raw.
18
+
19
+ ## [0.14.2] — 2026-09-15
20
+
21
+ ### Added
22
+
23
+ - **`yamine skills install` — one command into your harness's local skills
24
+ directory.** With `ask-skills` / `ask-agent` the bundled yamine skill
25
+ at `lib/ask/skills/yamine/SKILL.md` is auto-discovered. Without them,
26
+ most harnesses (including ZCode) already discover
27
+ `~/.agents/skills/` — this command copies there so nobody has to find
28
+ the gem path by hand: `yamine skills install` (global default),
29
+ `yamine skills install --local` (per-project), `yamine skills
30
+ install --dir <path>` (anywhere), with `uninstall` symmetric. The gem
31
+ now auto-updates that copy: the installed skill is marked with
32
+ `.yamine-managed` and every `yamine <anything>` silently refreshes it
33
+ when the bundled version differs — hand-copied skills are never
34
+ touched.
35
+
3
36
  ## [0.14.1] — 2026-09-13
4
37
 
5
38
  ### Removed
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module Yamine
6
+ class CLI
7
+ # Manages the yamine SKILL.md copy in conventional skill directories.
8
+ #
9
+ # The bundled skill lives inside the gem at lib/ask/skills/yamine/SKILL.md.
10
+ # With ask-skills / ask-agent it is auto-discovered. Without them, users
11
+ # still need it where harnesses look — most commonly ~/.agents/skills/.
12
+ # This command handles that copy so nobody has to find the gem path by hand.
13
+ module SkillsCommand
14
+ module_function
15
+
16
+ SKILL_NAME = "yamine"
17
+ SOURCE_REL = "ask/skills/yamine/SKILL.md"
18
+
19
+ # Keeps installed copies in sync after `gem update yamine`. Called
20
+ # once per yamine invocation, silently, before the subcommand runs.
21
+ # Only touches copies that were created by `yamine skills install`
22
+ # (identified by the marker file next to SKILL.md) so a hand-copied
23
+ # or hand-edited skill is never overwritten. Failures are swallowed
24
+ # — a stale skill is better than a broken boot.
25
+ def auto_sync
26
+ candidates.each do |dest|
27
+ next unless File.file?(dest)
28
+ next unless managed?(dest)
29
+ sync_copy(dest)
30
+ end
31
+ rescue StandardError
32
+ nil
33
+ end
34
+
35
+ def run(_ctx, args)
36
+ sub = args.shift
37
+ if sub == "--help" || sub == "-h" || sub.nil?
38
+ return install(["--help"])
39
+ end
40
+ case sub
41
+ when "install" then install(args)
42
+ when "uninstall" then uninstall(args)
43
+ when nil
44
+ raise Error, "Usage: yamine skills install [--global] [--local] [--dir <path>] | " \
45
+ "yamine skills uninstall [--global] [--local] [--dir <path>]"
46
+ else
47
+ raise Error, "Unknown skills subcommand #{sub.inspect}. " \
48
+ "Try: yamine skills install --global"
49
+ end
50
+ end
51
+
52
+ def install(args)
53
+ opts, rest = take_flags(args)
54
+ unless rest.empty?
55
+ raise Error, "Unknown argument #{rest.first.inspect}. " \
56
+ "Try: yamine skills install --global"
57
+ end
58
+ dest = target_dirs(opts).first
59
+ source = bundled_skill_path
60
+ unless File.file?(source)
61
+ raise Error, "Bundled skill not found at #{source} — reinstall yamine"
62
+ end
63
+ FileUtils.mkdir_p(File.dirname(dest))
64
+ FileUtils.cp(source, dest)
65
+ stamp(dest)
66
+ puts "Installed yamine skill -> #{dest}"
67
+ if opts[:dir].nil? && !opts[:local]
68
+ collisions = other_skill_dirs(opts)
69
+ existing = collisions.select { |p| File.file?(p) }
70
+ unless existing.empty?
71
+ puts "Note: also found at #{existing.join(", ")} (yamine is already available there)"
72
+ end
73
+ end
74
+ 0
75
+ end
76
+
77
+ def uninstall(args)
78
+ opts, rest = take_flags(args)
79
+ unless rest.empty?
80
+ raise Error, "Unknown argument #{rest.first.inspect}."
81
+ end
82
+ dest = target_dirs(opts).first
83
+ if File.file?(dest)
84
+ FileUtils.rm(dest)
85
+ FileUtils.rm_f(managed_marker(dest))
86
+ puts "Removed #{dest}"
87
+ else
88
+ puts "Not installed at #{dest} (nothing to do)"
89
+ end
90
+ 0
91
+ end
92
+
93
+ def target_dirs(opts)
94
+ return [File.expand_path(File.join(opts[:dir], SKILL_NAME, "SKILL.md"))] if opts[:dir]
95
+
96
+ if opts[:local]
97
+ [File.join(Dir.pwd, ".agents", "skills", SKILL_NAME, "SKILL.md")]
98
+ else
99
+ [File.expand_path("~/.agents/skills/#{SKILL_NAME}/SKILL.md")]
100
+ end
101
+ end
102
+
103
+ def other_skill_dirs(opts)
104
+ [
105
+ File.join(Dir.pwd, ".agents", "skills", SKILL_NAME, "SKILL.md"),
106
+ File.expand_path("~/.agents/skills/#{SKILL_NAME}/SKILL.md"),
107
+ File.expand_path("~/.config/ask/skills/#{SKILL_NAME}/SKILL.md")
108
+ ].reject { |p| p == target_dirs(opts).first }
109
+ end
110
+
111
+ def bundled_skill_path
112
+ File.expand_path("../../ask/skills/yamine/SKILL.md", __dir__)
113
+ end
114
+
115
+ def managed_marker(dest)
116
+ File.join(File.dirname(dest), ".yamine-managed")
117
+ end
118
+
119
+ def managed?(dest)
120
+ File.file?(managed_marker(dest))
121
+ end
122
+
123
+ def stamp(dest)
124
+ File.write(managed_marker(dest), "managed by yamine skills install; safe to auto-update\n")
125
+ rescue StandardError
126
+ nil
127
+ end
128
+
129
+ def candidates
130
+ [
131
+ File.expand_path("~/.agents/skills/#{SKILL_NAME}/SKILL.md"),
132
+ File.join(Dir.pwd, ".agents", "skills", SKILL_NAME, "SKILL.md")
133
+ ]
134
+ end
135
+
136
+ def sync_copy(dest)
137
+ source = bundled_skill_path
138
+ FileUtils.cp(source, dest) if File.exist?(source) && File.read(source) != File.read(dest)
139
+ stamp(dest)
140
+ rescue StandardError
141
+ nil
142
+ end
143
+
144
+ def take_flags(args)
145
+ opts = {}
146
+ rest = []
147
+ i = 0
148
+ while i < args.length
149
+ arg = args[i]
150
+ case arg
151
+ when "--local"
152
+ opts[:local] = true
153
+ i += 1
154
+ when "--global"
155
+ i += 1
156
+ when "--dir"
157
+ opts[:dir] = args.fetch(i + 1) { raise Error, "--dir needs a path" }
158
+ i += 2
159
+ when "--help", "-h"
160
+ puts <<~HELP
161
+ yamine skills install Install the yamine SKILL.md into your skills directories
162
+
163
+ Usage:
164
+ yamine skills install [--global] -> ~/.agents/skills/yamine/SKILL.md (default)
165
+ yamine skills install --local -> .agents/skills/yamine/SKILL.md
166
+ yamine skills install --dir <path> -> <path>/yamine/SKILL.md
167
+
168
+ The skill is already bundled at lib/ask/skills/yamine/SKILL.md inside the gem.
169
+ Most harnesses discover ~/.agents/skills/ automatically. Use --local for per-project.
170
+
171
+ Other subcommands:
172
+ yamine skills uninstall [--global|--local|--dir <path>]
173
+ HELP
174
+ exit 0
175
+ when /\A--/
176
+ raise Error, "Unknown flag #{arg} for yamine skills install. " \
177
+ "Try --global, --local, or --dir <path>."
178
+ else
179
+ rest << arg
180
+ i += 1
181
+ end
182
+ end
183
+ [opts, rest]
184
+ end
185
+ end
186
+ end
187
+ end
data/lib/yamine/cli.rb CHANGED
@@ -10,7 +10,7 @@ module Yamine
10
10
  # In non-interactive environments (no TTY or CI=1) we fail early with
11
11
  # a clear message instead of prompting (portless lesson).
12
12
  class CLI
13
- SUBCOMMANDS = %w[get alias hosts list doctor trust clean prune proxy service sudoers kamal stop restart log status open setup start init db worktree].freeze
13
+ SUBCOMMANDS = %w[get alias hosts list doctor trust clean prune proxy service sudoers kamal stop restart log status open setup start init db worktree skills].freeze
14
14
 
15
15
  def self.run(argv)
16
16
  new.run(argv)
@@ -24,6 +24,7 @@ module Yamine
24
24
  end
25
25
 
26
26
  def run(argv)
27
+ SkillsCommand.auto_sync rescue nil
27
28
  args = argv.dup
28
29
  if args.empty?
29
30
  return BootCommand.run_inferred(Context.new, args)
@@ -60,6 +61,7 @@ module Yamine
60
61
  when "start" then SystemCommand.start(ctx, args)
61
62
  when "db" then SystemCommand.db(ctx, args)
62
63
  when "worktree" then WorktreeCommand.run(ctx, args)
64
+ when "skills" then SkillsCommand.run(ctx, args)
63
65
  when "kamal" then SystemCommand.kamal(ctx, args)
64
66
  when "stop"
65
67
  exit RoutesCommand.stop(ctx, args)
@@ -94,6 +96,7 @@ module Yamine
94
96
  yamine prune Remove stale routes
95
97
  yamine db list|create|drop Per-worktree databases
96
98
  yamine worktree list|add|remove|clean Worktrees: create, inspect, full cleanup
99
+ yamine skills install Install yamine skill into ~/.agents/skills/ (or --local)
97
100
  yamine proxy start|stop Control the proxy
98
101
  yamine service install|status|uninstall OS startup service
99
102
  yamine sudoers Print scoped passwordless-sudo rules for port 443
data/lib/yamine/proxy.rb CHANGED
@@ -320,12 +320,18 @@ module Yamine
320
320
 
321
321
  def handle_upgrade(sock, head, buf)
322
322
  routes = cached_routes
323
- _method, _target, headers = parse_head(head)
323
+ method, target, headers = parse_head(head)
324
324
  entry = route(headers["host"].to_s, routes)
325
325
  return unless entry
326
326
 
327
+ # Upgrades carry the same forwarded headers as every other request:
328
+ # an app behind the proxy (ActionCable's origin check) reads the
329
+ # scheme from X-Forwarded-Proto, and a verbatim head would tell a
330
+ # wss:// connection it arrived as http.
331
+ set_forwarded(headers, sock, tls: @tls)
332
+
327
333
  backend = dial(entry)
328
- backend.write(head)
334
+ backend.write(rebuild_head(method, target, headers))
329
335
  backend.write(buf) unless buf.empty?
330
336
  pipe_both(sock, backend)
331
337
  rescue SystemCallError, OpenSSL::SSL::SSLError, IOError => e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yamine
4
- VERSION = "0.14.1"
4
+ VERSION = "0.14.3"
5
5
  end
data/lib/yamine.rb CHANGED
@@ -32,6 +32,7 @@ require_relative "yamine/cli/boot"
32
32
  require_relative "yamine/cli/routes"
33
33
  require_relative "yamine/cli/system"
34
34
  require_relative "yamine/cli/worktree"
35
+ require_relative "yamine/cli/skills"
35
36
  require_relative "yamine/cli"
36
37
 
37
38
  # Stable named .localhost URLs for Ruby development.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yamine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.1
4
+ version: 0.14.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -88,6 +88,7 @@ files:
88
88
  - lib/yamine/cli/boot.rb
89
89
  - lib/yamine/cli/context.rb
90
90
  - lib/yamine/cli/routes.rb
91
+ - lib/yamine/cli/skills.rb
91
92
  - lib/yamine/cli/system.rb
92
93
  - lib/yamine/cli/worktree.rb
93
94
  - lib/yamine/command.rb