hiiro 0.1.337 → 0.1.339

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: cdc7066a25c11d282e6e13914e89d1f17b93b1e6eebaadb3b78c445e9ad54730
4
- data.tar.gz: ccfe1dacaa0fadff21f621d56743d99066fec4f911de70a58741504b8a9330a3
3
+ metadata.gz: bcaeb672d1c537efc87a229a1db28b61533c42507dc9418732297d0564cb0903
4
+ data.tar.gz: 8d90d568d5a7d4b601a267deb60039c74acec7b1151fe1026dac06f60669bdf1
5
5
  SHA512:
6
- metadata.gz: 1c52eefc3e02bb0eadee1019f091facbfcd01509d6e57c32214032f901aebf3b0204afa1aacff3dfb3c4f9d793b298d11cf888fb02c6ddc24a0948c7737526c7
7
- data.tar.gz: f2cc1b1cfbfae9fc3c6229e140779deeee162458d2b04e1ac04a61ea515e13161f5d6a70c93316c5f1c7bfc895baa8552c809d199434e4b42dd2f90d45be91e2
6
+ metadata.gz: 43edf56c4056bb07b893c6c93a7b34cf2ea760d52f94f8c243835250c3bcf3c508a61ec3e7ab835d61808e8fc87bf4a400d9387a5afbe4e660c7ca32f30d084a
7
+ data.tar.gz: 63a2754cfebf1a3fba0d6145d9abd7df41b8b2d90a6a0342f2da9d23a9a3674bde800bce9b000d9de587e5de7e01ef7f7f0e7345f76a319c06b53895463067bf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.339] - 2026-04-07
4
+
5
+ ### Added
6
+ - `h ps byport <port> [port2 ...]` — find processes listening on specified port(s)
7
+ - `PsProcess.by_port(*ports)` — query processes by listening port numbers
8
+ - `h task switch` now matches ~/proj/* directories by prefix as fallback when task name doesn't match
9
+
10
+ ## [0.1.338] - 2026-04-07
11
+
12
+ ### Changed
13
+ - `h pr watch`, `h pr fwatch`, `h pr check` now default to current branch's PR (use `-s` to select via fuzzyfinder)
14
+ - Renamed `registry_entries` table to `registry` (auto-migrates existing data)
15
+
3
16
  ## [0.1.337] - 2026-04-06
4
17
 
5
18
  ### Changed
@@ -311,73 +324,4 @@
311
324
  - `bin/h-link` — reads/writes links via `Hiiro::Link` SQLite model with YAML dual-write fallback; adds `q`/`query` subcommands
312
325
  - `bin/h-pane` — load/save pane homes via `Hiiro::PaneHome` model with YAML dual-write
313
326
  - `bin/h-pr` — adds `q`/`query` subcommands for inspecting PR records via raw SQL
314
- - `plugins/pins.rb` — `Pin` class reads/writes via `Hiiro::PinRecord` SQLite model with YAML dual-write fallback
315
-
316
- ## [0.1.306] - 2026-03-30
317
-
318
- ### Changed
319
- - Increase delayed_update sleep duration from 5s to 15s
320
- - Add logging for delayed_update invocation in publish script
321
-
322
- ## [0.1.305] - 2026-03-30
323
-
324
- ### Changed
325
- - Refactor: use delayed_update subcommand instead of direct update call
326
- - Improve gem version matching regex in version check
327
-
328
- ## [0.1.304] - 2026-03-30
329
-
330
- ### Changed
331
- - h-notify: use universal log instead of per-session logging
332
- - Todo output simplified
333
-
334
- ## [0.1.302] - 2026-03-30
335
-
336
- ### Fixed
337
- - Truncate output lines to terminal width in tasks plugin
338
-
339
- ## [0.1.301]
340
-
341
- ### Added
342
- - Check version delayed update functionality
343
-
344
- ### Changed
345
- - h-claude: add verbose flags and refactor glob_path handling
346
-
347
- ### Fixed
348
- - Use exact session matching to prevent tmux prefix ambiguity
349
-
350
- ## [0.1.300]
351
-
352
- ### Added
353
- - h-claude: fulltext search option for agents/commands/skills
354
-
355
- ### Changed
356
- - Refactor h-claude directory traversal and file globbing
357
-
358
- ## [0.1.299]
359
-
360
- ### Added
361
- - h-pr open: support opening multiple PRs
362
-
363
- ## [0.1.298]
364
-
365
- ### Changed
366
- - Use Pathname to walk up directory tree
367
- - h-claude agents/commands/skills walk from pwd up to home
368
-
369
- ## [0.1.297]
370
-
371
- ### Added
372
- - h rnext subcommand
373
-
374
- ## [0.1.296]
375
-
376
- ### Changed
377
- - Refactor PR filter logic to pinned_pr_manager
378
- - Move PR filter logic to Pr#matches_filters?
379
-
380
- ## [0.1.295]
381
-
382
- ### Changed
383
- - Filter logic changes for PR management
327
+ - `plugins/pins.rb` — `Pin` class reads/writes via `Hiiro::PinRecord` SQLite model with YAML dual-write fallback
data/bin/h-pr CHANGED
@@ -11,11 +11,30 @@ Hiiro.run(*ARGV, plugins: [Pins]) do
11
11
  pm = pinned_manager = Hiiro::PinnedPRManager.new
12
12
  Hiiro::PinnedPRManager.add_resolvers(self)
13
13
 
14
- watch_block = ->(original_pr_number=nil, *watch_args) {
14
+ watch_block = ->(*all_args) {
15
+ opts = Hiiro::Options.setup {
16
+ option(:select, short: :s, type: :flag, desc: 'Select PR via fuzzyfinder')
17
+ }.parse(all_args)
18
+
19
+ original_pr_number = opts.args.first
20
+ watch_args = opts.args[1..] || []
15
21
  watch = get_value(:watch)
16
22
  fail_fast = get_value(:fail_fast)
17
23
 
18
- pr_number = resolve(:pr, original_pr_number)
24
+ pr_number = if opts.select
25
+ resolve(:pr, nil) # triggers fuzzyfinder
26
+ elsif original_pr_number
27
+ resolve(:pr, original_pr_number)
28
+ else
29
+ # Default to current branch's PR
30
+ current_pr = pm.fetch_current_branch_pr
31
+ if current_pr
32
+ current_pr.number.to_s
33
+ else
34
+ STDERR.puts "No PR for current branch. Use -s to select or provide a PR number."
35
+ nil
36
+ end
37
+ end
19
38
  return unless pr_number
20
39
 
21
40
  base_cmd = %w[gh pr checks]
data/bin/h-ps CHANGED
@@ -20,6 +20,21 @@ Hiiro.run(*ARGV) do
20
20
  end
21
21
  end
22
22
 
23
+ add_subcmd(:byport) { |*ports|
24
+ if ports.empty?
25
+ puts "Usage: h ps byport <port> [port2 ...]"
26
+ next
27
+ end
28
+
29
+ processes = Hiiro::PsProcess.by_port(*ports)
30
+
31
+ if processes.empty?
32
+ puts "No processes found on port(s): #{ports.join(', ')}"
33
+ else
34
+ processes.each { |p| puts "#{p.pid}\t#{p.dir || '(unknown)'}" }
35
+ end
36
+ }
37
+
23
38
  add_subcmd(:search) { |pattern = nil|
24
39
  if pattern.nil?
25
40
  puts "Usage: h ps search <pattern>"
data/bin/h-registry CHANGED
@@ -21,8 +21,9 @@ Hiiro.run(*ARGV, tasks: true) do
21
21
  puts '─' * 40
22
22
  end
23
23
  alias_col = e.short_name ? "[#{e.short_name}]".ljust(18) : ' ' * 18
24
+ value_col = e.value ? " = #{e.value}" : ''
24
25
  desc_col = e.description ? " # #{e.description}" : ''
25
- puts " #{alias_col} #{e.name}#{desc_col}"
26
+ puts " #{alias_col} #{e.name}#{value_col}#{desc_col}"
26
27
  end
27
28
  puts
28
29
  end
@@ -33,10 +34,10 @@ Hiiro.run(*ARGV, tasks: true) do
33
34
  puts types.empty? ? "No types registered yet." : types.join("\n")
34
35
  end
35
36
 
36
- # h registry add <type> <name> [--alias <short>] [--desc <description>]
37
+ # h registry add <type> <name> [value] [--alias <short>] [--desc <description>]
37
38
  add_subcmd(:add) do |type, name, *rest|
38
39
  unless type && name
39
- puts "Usage: h registry add <type> <name> [--alias <short>] [--desc <text>]"
40
+ puts "Usage: h registry add <type> <name> [value] [--alias <short>] [--desc <text>]"
40
41
  next
41
42
  end
42
43
  opts = Hiiro::Options.setup {
@@ -44,6 +45,9 @@ Hiiro.run(*ARGV, tasks: true) do
44
45
  option(:desc, short: :d, desc: 'Description')
45
46
  }.parse(rest)
46
47
 
48
+ # First positional arg after name is the value
49
+ value = opts.args.first
50
+
47
51
  existing = Hiiro::RegistryEntry.find_by_ref(name, type: type)
48
52
  if existing
49
53
  puts "Already registered: #{existing.display}"
@@ -53,6 +57,7 @@ Hiiro.run(*ARGV, tasks: true) do
53
57
  entry = Hiiro::RegistryEntry.create(
54
58
  resource_type: type,
55
59
  name: name,
60
+ value: value,
56
61
  short_name: opts.alias,
57
62
  description: opts.desc,
58
63
  created_at: Time.now.iso8601
@@ -60,15 +65,26 @@ Hiiro.run(*ARGV, tasks: true) do
60
65
  puts "Added:\n#{entry.display}"
61
66
  end
62
67
 
63
- # h registry rm <name_or_alias> [type]
64
- add_subcmd(:rm, :remove) do |ref, type = nil|
68
+ # h registry rm <name_or_alias> [type] [-s]
69
+ add_subcmd(:rm, :remove) do |*args|
70
+ opts = Hiiro::Options.setup {
71
+ option(:substring, short: :s, type: :flag, desc: 'Match substring anywhere in name')
72
+ }.parse(args)
73
+
74
+ ref, type = opts.args
65
75
  unless ref
66
- puts "Usage: h registry rm <name_or_alias> [type]"
76
+ puts "Usage: h registry rm <name_or_alias> [type] [-s]"
67
77
  next
68
78
  end
69
- entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type ? type.to_s : nil)
79
+ entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type&.to_s, substring: opts.substring)
70
80
  unless entry
71
- puts "Not found: #{ref}#{type ? " (type: #{type})" : ''}"
81
+ matches = Hiiro::RegistryEntry.find_all_by_ref(ref, type: type&.to_s, substring: opts.substring)
82
+ if matches.any?
83
+ puts "Ambiguous match '#{ref}' - #{matches.size} entries:"
84
+ matches.each { |e| puts " #{e.resource_type}/#{e.name}" }
85
+ else
86
+ puts "Not found: #{ref}#{type ? " (type: #{type})" : ''}"
87
+ end
72
88
  next
73
89
  end
74
90
  puts "Removing: #{entry.display}"
@@ -76,24 +92,52 @@ Hiiro.run(*ARGV, tasks: true) do
76
92
  puts "Done."
77
93
  end
78
94
 
79
- # h registry get <name_or_alias> [type] — prints canonical name (scriptable)
80
- add_subcmd(:get) do |ref, type = nil|
95
+ # h registry get <name_or_alias> [type] [-s] — prints value (or name if no value)
96
+ # Supports prefix matching; use -s/--substring for substring match
97
+ # Prints all matching values (one per line) if multiple matches
98
+ add_subcmd(:get) do |*args|
99
+ opts = Hiiro::Options.setup {
100
+ option(:substring, short: :s, type: :flag, desc: 'Match substring anywhere in name')
101
+ }.parse(args)
102
+
103
+ ref, type = opts.args
81
104
  unless ref
82
- puts "Usage: h registry get <name_or_alias> [type]"
105
+ puts "Usage: h registry get <name_or_alias> [type] [-s]"
83
106
  next
84
107
  end
85
- entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type ? type.to_s : nil)
86
- entry ? puts(entry.name) : exit(1)
108
+
109
+ matches = Hiiro::RegistryEntry.find_all_by_ref(ref, type: type&.to_s, substring: opts.substring)
110
+ if matches.empty?
111
+ exit(1)
112
+ else
113
+ matches.each { |e| puts(e.value || e.name) }
114
+ end
87
115
  end
88
116
 
89
- # h registry show <name_or_alias> [type] — human-readable detail
90
- add_subcmd(:show) do |ref, type = nil|
117
+ # h registry show <name_or_alias> [type] [-s] — human-readable detail
118
+ add_subcmd(:show) do |*args|
119
+ opts = Hiiro::Options.setup {
120
+ option(:substring, short: :s, type: :flag, desc: 'Match substring anywhere in name')
121
+ }.parse(args)
122
+
123
+ ref, type = opts.args
91
124
  unless ref
92
- puts "Usage: h registry show <name_or_alias> [type]"
125
+ puts "Usage: h registry show <name_or_alias> [type] [-s]"
93
126
  next
94
127
  end
95
- entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type ? type.to_s : nil)
96
- entry ? puts(entry.display) : (puts "Not found: #{ref}"; exit 1)
128
+ entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type&.to_s, substring: opts.substring)
129
+ if entry
130
+ puts entry.display
131
+ else
132
+ matches = Hiiro::RegistryEntry.find_all_by_ref(ref, type: type&.to_s, substring: opts.substring)
133
+ if matches.any?
134
+ puts "Ambiguous match '#{ref}' - #{matches.size} entries:"
135
+ matches.each { |e| puts " #{e.resource_type}/#{e.name}" }
136
+ else
137
+ puts "Not found: #{ref}"
138
+ end
139
+ exit 1
140
+ end
97
141
  end
98
142
 
99
143
  # h registry select [type] — fuzzyfinder, prints canonical name
@@ -110,19 +154,50 @@ Hiiro.run(*ARGV, tasks: true) do
110
154
  puts name
111
155
  end
112
156
 
113
- # h registry set-alias <name_or_alias> <new_alias> [type]
114
- add_subcmd(:'set-alias') do |ref, new_alias, type = nil|
157
+ # h registry set-alias <name_or_alias> <new_alias> [type] [-s]
158
+ add_subcmd(:'set-alias') do |*args|
159
+ opts = Hiiro::Options.setup {
160
+ option(:substring, short: :s, type: :flag, desc: 'Match substring anywhere in name')
161
+ }.parse(args)
162
+
163
+ ref, new_alias, type = opts.args
115
164
  unless ref && new_alias
116
- puts "Usage: h registry set-alias <name_or_alias> <new_alias> [type]"
165
+ puts "Usage: h registry set-alias <name_or_alias> <new_alias> [type] [-s]"
117
166
  next
118
167
  end
119
- entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type ? type.to_s : nil)
168
+ entry = Hiiro::RegistryEntry.find_by_ref(ref, type: type&.to_s, substring: opts.substring)
120
169
  unless entry
121
- puts "Not found: #{ref}"
170
+ matches = Hiiro::RegistryEntry.find_all_by_ref(ref, type: type&.to_s, substring: opts.substring)
171
+ if matches.any?
172
+ puts "Ambiguous match '#{ref}' - #{matches.size} entries:"
173
+ matches.each { |e| puts " #{e.resource_type}/#{e.name}" }
174
+ else
175
+ puts "Not found: #{ref}"
176
+ end
122
177
  next
123
178
  end
124
179
  entry.update(short_name: new_alias)
125
180
  puts entry.display
126
181
  end
127
182
 
183
+ # h registry set <type> <name> <value> — update value of existing entry (or create)
184
+ add_subcmd(:set, :update) do |type, name, value = nil|
185
+ unless type && name
186
+ puts "Usage: h registry set <type> <name> <value>"
187
+ next
188
+ end
189
+ entry = Hiiro::RegistryEntry.find_by_ref(name, type: type)
190
+ if entry
191
+ entry.update(value: value)
192
+ else
193
+ entry = Hiiro::RegistryEntry.create(
194
+ resource_type: type,
195
+ name: name,
196
+ value: value,
197
+ created_at: Time.now.iso8601
198
+ )
199
+ end
200
+ puts entry.display
201
+ end
202
+
128
203
  end
data/lib/hiiro/db.rb CHANGED
@@ -37,6 +37,8 @@ class Hiiro
37
37
 
38
38
  MODELS.each do |cls|
39
39
  cls.create_table!(conn)
40
+ # Run model-specific migrations (e.g., adding columns to existing tables)
41
+ cls.migrate!(conn) if cls.respond_to?(:migrate!)
40
42
  # Clear all schema-related caches on the model and its anonymous Sequel
41
43
  # parent class. When require_valid_table=false and the table didn't exist
42
44
  # at class-definition time, Sequel caches empty results for @db_schema,
@@ -54,6 +54,22 @@ class Hiiro::PsProcess
54
54
  all.find { |p| p.pid == pid.to_s }
55
55
  end
56
56
 
57
+ # Find processes listening on given port numbers
58
+ def self.by_port(*ports)
59
+ pids = Set.new
60
+ ports.each do |port|
61
+ lsof_output = `lsof -i :#{port.to_i} 2>/dev/null`.lines[1..]
62
+ next unless lsof_output
63
+
64
+ lsof_output.each do |line|
65
+ fields = line.split
66
+ pids << fields[1] if fields[1]
67
+ end
68
+ end
69
+
70
+ all.select { |p| pids.include?(p.pid) }
71
+ end
72
+
57
73
  # Find processes with files open in given directories
58
74
  def self.in_dirs(*paths)
59
75
  pids = Set.new
@@ -1,14 +1,20 @@
1
1
  require 'sequel'
2
2
 
3
3
  class Hiiro
4
- class RegistryEntry < Sequel::Model(:registry_entries)
4
+ class RegistryEntry < Sequel::Model(:registry)
5
5
  Hiiro::DB.register(self)
6
6
 
7
7
  def self.create_table!(db)
8
- db.create_table?(:registry_entries) do
8
+ # Rename old table if it exists
9
+ if db.table_exists?(:registry_entries) && !db.table_exists?(:registry)
10
+ db.rename_table(:registry_entries, :registry)
11
+ end
12
+
13
+ db.create_table?(:registry) do
9
14
  primary_key :id
10
15
  String :resource_type, null: false # e.g. "service", "queue", "worker"
11
16
  String :name, null: false # canonical identifier
17
+ String :value # stored value (optional)
12
18
  String :short_name # alias / shorthand
13
19
  String :description
14
20
  String :meta_json # arbitrary JSON for extra fields
@@ -18,16 +24,65 @@ class Hiiro
18
24
  end
19
25
  end
20
26
 
27
+ def self.migrate!(db)
28
+ # Add value column if missing (migration for existing DBs)
29
+ unless db.schema(:registry).any? { |col, _| col == :value }
30
+ db.alter_table(:registry) { add_column :value, String }
31
+ end
32
+ end
33
+
21
34
  # ── Finders ──────────────────────────────────────────────────────────────
22
35
 
23
36
  def self.of_type(type) = where(resource_type: type.to_s).order(:name).all
24
37
  def self.all_ordered = order(:resource_type, :name).all
25
38
  def self.known_types = distinct.select_map(:resource_type).sort
26
39
 
27
- # Resolve by exact name or short_name within a type (or globally).
28
- def self.find_by_ref(ref, type: nil)
40
+ # Resolve by exact name, short_name, or prefix within a type (or globally).
41
+ # Returns single entry or nil. Ambiguous prefix matches return nil.
42
+ def self.find_by_ref(ref, type: nil, substring: false)
43
+ scope = type ? where(resource_type: type.to_s) : self
44
+
45
+ # Try exact match first (name or short_name)
46
+ exact = scope.where(name: ref).first || scope.where(short_name: ref).first
47
+ return exact if exact
48
+
49
+ # Fall back to prefix/substring matching
50
+ entries = scope.all
51
+ matcher = Hiiro::Matcher.new(entries, :name)
52
+ result = substring ? matcher.by_substring(ref) : matcher.by_prefix(ref)
53
+
54
+ # Only return if unambiguous (exactly one match)
55
+ return result.first.item if result.one?
56
+
57
+ # Also try matching against short_name
58
+ matcher_short = Hiiro::Matcher.new(entries.select(&:short_name), :short_name)
59
+ result_short = substring ? matcher_short.by_substring(ref) : matcher_short.by_prefix(ref)
60
+ return result_short.first.item if result_short.one?
61
+
62
+ nil
63
+ end
64
+
65
+ # Find all entries matching ref (for showing ambiguous matches)
66
+ # Checks both name and short_name, returns unique entries
67
+ def self.find_all_by_ref(ref, type: nil, substring: false)
29
68
  scope = type ? where(resource_type: type.to_s) : self
30
- scope.where(name: ref).first || scope.where(short_name: ref).first
69
+
70
+ # Check for exact matches first
71
+ exact = scope.where(name: ref).all + scope.where(short_name: ref).all
72
+ return exact.uniq(&:id) if exact.any?
73
+
74
+ # Fall back to prefix/substring matching on name
75
+ entries = scope.all
76
+ matcher = Hiiro::Matcher.new(entries, :name)
77
+ result = substring ? matcher.by_substring(ref) : matcher.by_prefix(ref)
78
+ name_matches = result.matches.map(&:item)
79
+
80
+ # Also match on short_name
81
+ matcher_short = Hiiro::Matcher.new(entries.select(&:short_name), :short_name)
82
+ result_short = substring ? matcher_short.by_substring(ref) : matcher_short.by_prefix(ref)
83
+ short_matches = result_short.matches.map(&:item)
84
+
85
+ (name_matches + short_matches).uniq(&:id)
31
86
  end
32
87
 
33
88
  # Fuzzy-finder display lines: "type short name description"
@@ -51,12 +106,14 @@ class Hiiro
51
106
 
52
107
  def fuzzy_line
53
108
  parts = [resource_type.ljust(14), short_name&.ljust(16) || ' ' * 16, name]
109
+ parts << " = #{value}" if value && !value.empty?
54
110
  parts << " # #{description}" if description && !description.empty?
55
111
  parts.join(' ').strip
56
112
  end
57
113
 
58
114
  def display
59
115
  lines = ["#{resource_type} / #{name}"]
116
+ lines << " value: #{value}" if value
60
117
  lines << " alias: #{short_name}" if short_name
61
118
  lines << " desc: #{description}" if description
62
119
  meta.each { |k, v| lines << " #{k}: #{v}" } unless meta.empty?
data/lib/hiiro/tasks.rb CHANGED
@@ -964,6 +964,22 @@ class Hiiro
964
964
  end
965
965
  end
966
966
 
967
+ # If still no task/session found, check ~/proj/* directories by prefix
968
+ unless task
969
+ proj_dirs = Dir.glob(File.expand_path('~/proj/*/'))
970
+ dir_names = proj_dirs.map { |d| File.basename(d) }
971
+ result = Hiiro::Matcher.by_prefix(dir_names, task_name)
972
+ if result.one?
973
+ dir_name = result.first.item
974
+ h.start_tmux_session(dir_name, start_directory: File.expand_path("~/proj/#{dir_name}"))
975
+ puts "Switched to ~/proj/#{dir_name}"
976
+ next
977
+ elsif result.ambiguous?
978
+ puts "Ambiguous match for '#{task_name}': #{result.matches.map(&:item).join(', ')}"
979
+ exit 1
980
+ end
981
+ end
982
+
967
983
  tm.switch_to_task(task, app_name: app_name, force: opts.force)
968
984
  end
969
985
 
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.337"
2
+ VERSION = "0.1.339"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiiro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.337
4
+ version: 0.1.339
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota