yamine 0.14.0 → 0.14.2

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: ab7c7e888dc8066ad10a7ed2fa90dcd684b27aa171038ac0ae5ea4c06b22251b
4
- data.tar.gz: '097d12693a71cf3969f481fd0f61b3f82e9a09d7958139e32b3fb79a7e667cd8'
3
+ metadata.gz: 56dbba9d974dc247a9f96271e6dd40e0e68b170a6ad20f9e0660aa6fa1d20a28
4
+ data.tar.gz: 218da1110324af22c665581eeb26101a29628f1f4e96fed47e492aebd6c18488
5
5
  SHA512:
6
- metadata.gz: 99ddf961f8fa7e50038cb093b592f692c4c90a94279dc200b9ab7ea9e8a70355e6bf67659bf0cb53aac66d2868d99c6fc138750e490aa1fa4ff5b606bbfacc3f
7
- data.tar.gz: 825fb69b730125767e18472ca3d7d615514660b63dbcb7aa63697970f6733b1497997b505a6fde9f1e6b0c62785127fac0d2b1332544ff4d535b4076680623b8
6
+ metadata.gz: 9e26fe517122747c2793eb2b441fe83289898796d47c99330d2589413873fa7a12ae1cc2a8b1da69928b39bc6f8ed01aa6e7647f682a872c80886d04d1a862be
7
+ data.tar.gz: ce6b77f5a0263594e698cae37ddd3673a8dc6e16a63b946f8bd0f3b939747d80ce86bbc19e100da84a4a7b38aa942de39d86cdc509e348d71d817bc7a6def433
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.14.2] — 2026-09-15
4
+
5
+ ### Added
6
+
7
+ - **`yamine skills install` — one command into your harness's local skills
8
+ directory.** With `ask-skills` / `ask-agent` the bundled yamine skill
9
+ at `lib/ask/skills/yamine/SKILL.md` is auto-discovered. Without them,
10
+ most harnesses (including ZCode) already discover
11
+ `~/.agents/skills/` — this command copies there so nobody has to find
12
+ the gem path by hand: `yamine skills install` (global default),
13
+ `yamine skills install --local` (per-project), `yamine skills
14
+ install --dir <path>` (anywhere), with `uninstall` symmetric. The gem
15
+ now auto-updates that copy: the installed skill is marked with
16
+ `.yamine-managed` and every `yamine <anything>` silently refreshes it
17
+ when the bundled version differs — hand-copied skills are never
18
+ touched.
19
+
20
+ ## [0.14.1] — 2026-09-13
21
+
22
+ ### Removed
23
+
24
+ - **`yamine run` has been removed.** `run` was an alias of `start` since
25
+ the config-first rewrite but kept its old help text, and the
26
+ compound-line error advice still pointed at `yamine run -- <cmd>` —
27
+ a command that silently ignores its argument. The subcommand now
28
+ prints the migration message every other removed command uses ("all
29
+ processes come from config/local.yml"), and the advice rewrites to
30
+ what is actually true: split the command into its own process in
31
+ config, or wrap it in a script. One-off routed commands (Docker,
32
+ ad-hoc ports) go through `yamine alias`, which gives them a hostname
33
+ without yamine owning the process lifecycle.
34
+
3
35
  ## [0.14.0] — 2026-09-13
4
36
 
5
37
  ### Added
data/README.md CHANGED
@@ -134,8 +134,8 @@ case) need `yamine hosts sync`.
134
134
 
135
135
  | Axis | Example | Source |
136
136
  |---|---|---|
137
- | app | `myapp` | inferred, `--name`, `yamine.json`, `YAMINE_NAME` |
138
- | service | `api.myapp` | `--service`, `YAMINE_SERVICE` (`web` stays bare) |
137
+ | app | `myapp` | `service:` in `config/local.yml` (`yamine init` infers it) |
138
+ | service | `api.myapp` | a non-web process name (every `proxy: true` process but `web`) |
139
139
  | variant | `fix-ui.myapp` | `--variant`, `YAMINE_VARIANT`, linked worktree branch |
140
140
  | tld | `myapp.preview.example.com` | `--tld` (default `localhost`) |
141
141
 
@@ -186,7 +186,6 @@ worktrees (the branch stays); `--dry-run` prints the plan; `remove
186
186
 
187
187
  ```bash
188
188
  yamine # -> https://myapp.localhost
189
- yamine --service api # -> https://api.myapp.localhost
190
189
  yamine --variant demo # -> https://demo.myapp.localhost
191
190
  yamine --tld preview.example.com # your own domain (OAuth parity)
192
191
  ```
@@ -217,7 +216,6 @@ to start it. `yamine status` reports which mode an app is in.
217
216
  yamine # boot app (waits until healthy, then supervises)
218
217
  yamine start --no-wait # fire-and-forget (register routes immediately)
219
218
  yamine start --json # machine-readable wait result (--wait default)
220
- yamine run -- <cmd> # run explicit command through proxy
221
219
  yamine get <name> # print URL for cross-service wiring
222
220
  yamine alias <name> <port> # static route (e.g. Docker)
223
221
  yamine list [--json] # show active routes (+ backend liveness)
@@ -244,18 +242,13 @@ OAuth callbacks, mailer hosts, webhook URLs), `PORT`, and `HOST`.
244
242
  ## Frameworks
245
243
 
246
244
  Rails and bare Rack (`config.ru`) boot managed on a unix socket (Puma
247
- when available; `rackup` on TCP otherwise). `Procfile.dev`/`bin/dev`,
248
- Jekyll, Bridgetown, and Middleman run in run mode with `PORT` injected
249
- (port-ignoring CLIs get explicit `--port/--host` flags). Anything else:
250
- `yamine run -- <cmd>`.
245
+ when available; `rackup` on TCP otherwise). Port-ignoring CLIs —
246
+ Jekyll, Bridgetown, Middleman — get explicit `--port/--host` flags
247
+ injected at boot; everything else comes from `processes:` in
248
+ `config/local.yml`, one process per entry.
251
249
 
252
- ```bash
253
- yamine --proc web # boot a specific Procfile process
254
- yamine --proc worker # (first line is the default)
255
- ```
256
-
257
- Procfile lines that are compound (`&&`, `||`, `|`, `;`) are refused with
258
- guidance rather than silently mis-injected.
250
+ Process commands that are compound (`&&`, `||`, `|`, `;`) are refused
251
+ with guidance rather than silently mis-injected.
259
252
 
260
253
  For Rails integration (hosts, Action Cable origins, Procfile rewrite,
261
254
  generators) — deprecated; core covers Rails now.
@@ -42,10 +42,6 @@ module Yamine
42
42
  opts[:json] ? $stderr.puts(message) : puts(message)
43
43
  end
44
44
 
45
- def run_explicit(ctx, args)
46
- run_inferred(ctx, args)
47
- end
48
-
49
45
  def run_named(ctx, name, _args)
50
46
  if name.to_s.start_with?("-")
51
47
  $stderr.puts "Error: unknown flag `#{name}`. Try `yamine --help` or `yamine start --help`."
@@ -217,7 +213,7 @@ module Yamine
217
213
  url = Hostname.url(hostname, port: ctx.proxy_port, tls: ctx.proxy_tls)
218
214
  cmd = entry["cmd"].to_s
219
215
  if cmd.match?(Yamine::Procfile::COMPOUND)
220
- $stderr.puts " [#{proc_name}] ERROR: compound line (&&, ||, |, ;) — run explicitly: yamine run -- #{cmd}"
216
+ $stderr.puts " [#{proc_name}] ERROR: compound line (&&, ||, |, ;) — split it into separate processes, or wrap in a script and point cmd: at it"
221
217
  next
222
218
  end
223
219
  port = opts[:app_port] || Ports.find_free
@@ -356,7 +352,7 @@ module Yamine
356
352
  def boot_background(ctx, runner, resolved, proc_name, entry, opts, children, db_url)
357
353
  cmd = entry["cmd"].to_s
358
354
  if cmd.match?(Yamine::Procfile::COMPOUND)
359
- $stderr.puts " [#{proc_name}] ERROR: compound line (&&, ||, |, ;) — run explicitly: yamine run -- #{cmd}"
355
+ $stderr.puts " [#{proc_name}] ERROR: compound line (&&, ||, |, ;) — split it into separate processes, or wrap in a script and point cmd: at it"
360
356
  return
361
357
  end
362
358
  port = opts[:app_port] || Ports.find_free
@@ -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[run 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)
@@ -44,7 +45,6 @@ module Yamine
44
45
  cmd = args.shift
45
46
  ctx = Context.new
46
47
  case cmd
47
- when "run" then BootCommand.run_explicit(ctx, args)
48
48
  when "get" then RoutesCommand.get(ctx, args)
49
49
  when "alias" then RoutesCommand.alias_add(ctx, args)
50
50
  when "hosts" then SystemCommand.hosts(ctx, args)
@@ -61,6 +61,7 @@ module Yamine
61
61
  when "start" then SystemCommand.start(ctx, args)
62
62
  when "db" then SystemCommand.db(ctx, args)
63
63
  when "worktree" then WorktreeCommand.run(ctx, args)
64
+ when "skills" then SkillsCommand.run(ctx, args)
64
65
  when "kamal" then SystemCommand.kamal(ctx, args)
65
66
  when "stop"
66
67
  exit RoutesCommand.stop(ctx, args)
@@ -81,11 +82,9 @@ module Yamine
81
82
  yamine - Stable named .localhost URLs for Ruby development.
82
83
 
83
84
  Usage:
84
- yamine start [name] [cmd...] One-setup-and-go: setup if needed, then boot -> https://<app>.localhost
85
+ yamine start One-setup-and-go: setup if needed, then boot -> https://<app>.localhost
85
86
  yamine setup One-shot workstation setup without booting (run once)
86
- yamine Bare form of `start` -> https://<app>.localhost
87
- yamine run [cmd] Same, with explicit command
88
- yamine <name> <cmd> Run with explicit name
87
+ yamine Bare form of `start` — boots every process in config/local.yml
89
88
  yamine get <name> Print URL for a service
90
89
  yamine alias <name> <port> Static route (e.g. Docker)
91
90
  yamine list Show active routes (+ backend liveness)
@@ -97,6 +96,7 @@ module Yamine
97
96
  yamine prune Remove stale routes
98
97
  yamine db list|create|drop Per-worktree databases
99
98
  yamine worktree list|add|remove|clean Worktrees: create, inspect, full cleanup
99
+ yamine skills install Install yamine skill into ~/.agents/skills/ (or --local)
100
100
  yamine proxy start|stop Control the proxy
101
101
  yamine service install|status|uninstall OS startup service
102
102
  yamine sudoers Print scoped passwordless-sudo rules for port 443
@@ -106,8 +106,8 @@ module Yamine
106
106
  yamine restart Touch tmp/restart.txt
107
107
  yamine log [-f] [n] Tail (or follow) this app's backend log
108
108
 
109
- Flags: --variant, --tld, --force, --app-port, --wait (default), --no-wait, --json
110
- Env: YAMINE_NAME/SERVICE/VARIANT/TLD/PORT/STATE_DIR, YAMINE_BRANCH=1
109
+ Flags: --variant, --tld, --force, --app-port, --wait (default), --no-wait, --json, --branch
110
+ Env: YAMINE_VARIANT/TLD/PORT/STATE_DIR/AGENT, YAMINE_BRANCH=1
111
111
  HELP
112
112
  end
113
113
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Yamine
4
- VERSION = "0.14.0"
4
+ VERSION = "0.14.2"
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.0
4
+ version: 0.14.2
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