hiiro 0.1.51 → 0.1.52

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: 6181ad8d712bc544591aaa46f8c5c555b33f76cf6ed572cb70369f1f56b91bd9
4
- data.tar.gz: 1a7490eb2e795ffab86b7b4ad1f24fe46fc6d0ba508e76bc074a88470b1eb437
3
+ metadata.gz: 16885dd29890048ddde89a6423f9f369c30960bc77b32a330becf2a4cabe4d9e
4
+ data.tar.gz: a76ae53eced544b5d3ae231718c3423be4a6af4cee32c7fea9a1a9b66b0b2f4c
5
5
  SHA512:
6
- metadata.gz: 2ad79ec0a1e709d3cc7ce65e3142c23612b34f4b63e15d2811671cdca96858744f9dc57d2541b297df6e3abddaf61714ffe06f9491d694622ce129e65a3d5841
7
- data.tar.gz: 7e9e81804965cb3d58a7baa8a2d2ed1597ff474ec0cf56998a86328fe91014286ff7604d7faf6940b6e09e91453afd357285f2be3d2160fb22455dea65275ec6
6
+ metadata.gz: 6475ba7d65044917c5511806cd5e51b8a20c5316fa92f3470c025deca0d199abde8972e72357eb9d0cdcd94f323018c1286c984f6c649b12c264da34bd830ca8
7
+ data.tar.gz: dfdc403a2e94bcaba0de0c6bd7d60e94faa5e55fd2a04a087fc3a4dea6ece9bf560e9c97925a3955f65385fe2f24dd601378fad53e905114fca0b289763f0095
data/bin/h-app CHANGED
@@ -11,6 +11,10 @@ def load_apps
11
11
  YAML.safe_load_file(APPS_FILE) || {}
12
12
  end
13
13
 
14
+ def save_apps(apps)
15
+ File.write(APPS_FILE, apps.to_yaml)
16
+ end
17
+
14
18
  def task_root
15
19
  # Fall back to current task's tree path if not in a git repo
16
20
  env = Environment.current rescue nil
@@ -135,4 +139,70 @@ Hiiro.run(*ARGV, plugins: [:Tasks]) {
135
139
  puts "App '#{app_name}' not found"
136
140
  end
137
141
  }
142
+
143
+ add_subcmd(:add) { |app_name=nil, relative_path=nil|
144
+ if app_name.nil? || app_name.empty?
145
+ puts "Usage: h app add <app_name> <relative_path_from_root>"
146
+ next
147
+ end
148
+
149
+ if relative_path.nil? || relative_path.empty?
150
+ puts "Usage: h app add <app_name> <relative_path_from_root>"
151
+ next
152
+ end
153
+
154
+ apps = load_apps
155
+
156
+ if apps.key?(app_name)
157
+ puts "App '#{app_name}' already exists with path: #{apps[app_name]}"
158
+ puts "Remove it first with: h app rm #{app_name}"
159
+ next
160
+ end
161
+
162
+ apps[app_name] = relative_path
163
+ save_apps(apps)
164
+ puts "Added app '#{app_name}' => #{relative_path}"
165
+ }
166
+
167
+ add_subcmd(:rm) { |app_name=nil|
168
+ if app_name.nil? || app_name.empty?
169
+ puts "Usage: h app rm <app_name>"
170
+ next
171
+ end
172
+
173
+ apps = load_apps
174
+
175
+ unless apps.key?(app_name)
176
+ puts "App '#{app_name}' not found"
177
+ puts
178
+ puts "Available apps:"
179
+ apps.each { |name, path| puts format(" %-20s => %s", name, path) }
180
+ next
181
+ end
182
+
183
+ removed_path = apps.delete(app_name)
184
+ save_apps(apps)
185
+ puts "Removed app '#{app_name}' (was: #{removed_path})"
186
+ }
187
+
188
+ add_subcmd(:remove) { |app_name=nil|
189
+ if app_name.nil? || app_name.empty?
190
+ puts "Usage: h app remove <app_name>"
191
+ next
192
+ end
193
+
194
+ apps = load_apps
195
+
196
+ unless apps.key?(app_name)
197
+ puts "App '#{app_name}' not found"
198
+ puts
199
+ puts "Available apps:"
200
+ apps.each { |name, path| puts format(" %-20s => %s", name, path) }
201
+ next
202
+ end
203
+
204
+ removed_path = apps.delete(app_name)
205
+ save_apps(apps)
206
+ puts "Removed app '#{app_name}' (was: #{removed_path})"
207
+ }
138
208
  }
data/bin/h-link CHANGED
@@ -10,7 +10,7 @@ require "hiiro"
10
10
  class LinkManager
11
11
  LINKS_FILE = File.join(Dir.home, '.config/hiiro/links.yml')
12
12
  LINK_TEMPLATE = {
13
- 'url' => 'https://',
13
+ 'url' => '',
14
14
  'description' => '',
15
15
  'shorthand' => nil,
16
16
  'created_at' => Time.now.iso8601,
data/bin/h-pane CHANGED
@@ -60,4 +60,15 @@ o.add_subcmd(:resize) { |*args|
60
60
  system('tmux', 'resize-pane', *args)
61
61
  }
62
62
 
63
+ o.add_subcmd(:history) { |pane_id=nil|
64
+ entries = Hiiro::History.by_pane(pane_id)
65
+ if entries.empty?
66
+ puts "No history for this pane"
67
+ else
68
+ puts "History for pane: #{pane_id || ENV['TMUX_PANE'] || 'current'}"
69
+ puts
70
+ entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
71
+ end
72
+ }
73
+
63
74
  o.run
data/bin/h-session CHANGED
@@ -60,4 +60,16 @@ o.add_subcmd(:info) { |*args|
60
60
  system('tmux', 'display-message', '-p', '#{session_name}: #{session_windows} windows, #{session_attached} attached', *args)
61
61
  }
62
62
 
63
+ o.add_subcmd(:history) { |session_name=nil|
64
+ session_name ||= `tmux display-message -p '#S'`.strip if ENV['TMUX']
65
+ entries = Hiiro::History.by_session(session_name)
66
+ if entries.empty?
67
+ puts "No history for this session"
68
+ else
69
+ puts "History for session: #{session_name || 'current'}"
70
+ puts
71
+ entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
72
+ end
73
+ }
74
+
63
75
  o.run
data/bin/h-window CHANGED
@@ -56,4 +56,16 @@ o.add_subcmd(:unlink) { |*args|
56
56
  system('tmux', 'unlink-window', *args)
57
57
  }
58
58
 
59
+ o.add_subcmd(:history) { |window_id=nil|
60
+ window_id ||= `tmux display-message -p '#I'`.strip if ENV['TMUX']
61
+ entries = Hiiro::History.by_window(window_id)
62
+ if entries.empty?
63
+ puts "No history for this window"
64
+ else
65
+ puts "History for window: #{window_id || 'current'}"
66
+ puts
67
+ entries.last(20).each_with_index { |e, i| puts e.oneline(i + 1) }
68
+ end
69
+ }
70
+
59
71
  o.run
data/lib/hiiro/history.rb CHANGED
@@ -1,16 +1,23 @@
1
1
  require 'yaml'
2
2
  require 'time'
3
3
  require 'fileutils'
4
+ require 'digest'
4
5
 
5
6
  class Hiiro
6
7
  class History
7
- HISTORY_FILE = File.join(Dir.home, '.config/hiiro/history.yml')
8
+ HISTORY_DIR = File.join(Dir.home, '.config/hiiro/history')
9
+ HISTORY_FILE = File.join(HISTORY_DIR, 'entries.yml')
10
+ LAST_STATE_FILE = File.join(HISTORY_DIR, 'last_state.yml')
8
11
 
9
12
  class Entry
10
- attr_reader :id, :timestamp, :source, :cmd, :description
11
- attr_reader :tmux_session, :tmux_window, :tmux_pane
12
- attr_reader :git_branch, :git_worktree
13
- attr_reader :task, :subtask
13
+ FIELDS = %i[
14
+ id timestamp source cmd description pwd
15
+ tmux_session tmux_window tmux_pane
16
+ git_branch git_sha git_origin_sha git_worktree
17
+ task subtask app
18
+ ].freeze
19
+
20
+ attr_reader(*FIELDS)
14
21
 
15
22
  def initialize(data)
16
23
  data ||= {}
@@ -19,13 +26,17 @@ class Hiiro
19
26
  @source = data['source']
20
27
  @cmd = data['cmd']
21
28
  @description = data['description']
29
+ @pwd = data['pwd']
22
30
  @tmux_session = data['tmux_session']
23
31
  @tmux_window = data['tmux_window']
24
32
  @tmux_pane = data['tmux_pane']
25
33
  @git_branch = data['git_branch']
34
+ @git_sha = data['git_sha']
35
+ @git_origin_sha = data['git_origin_sha']
26
36
  @git_worktree = data['git_worktree']
27
37
  @task = data['task']
28
38
  @subtask = data['subtask']
39
+ @app = data['app']
29
40
  end
30
41
 
31
42
  def to_h
@@ -35,26 +46,62 @@ class Hiiro
35
46
  'source' => source,
36
47
  'cmd' => cmd,
37
48
  'description' => description,
49
+ 'pwd' => pwd,
50
+ 'tmux_session' => tmux_session,
51
+ 'tmux_window' => tmux_window,
52
+ 'tmux_pane' => tmux_pane,
53
+ 'git_branch' => git_branch,
54
+ 'git_sha' => git_sha,
55
+ 'git_origin_sha' => git_origin_sha,
56
+ 'git_worktree' => git_worktree,
57
+ 'task' => task,
58
+ 'subtask' => subtask,
59
+ 'app' => app,
60
+ }.compact
61
+ end
62
+
63
+ # Data used for change detection (excludes timestamp, id, cmd)
64
+ def state_key
65
+ {
66
+ 'pwd' => pwd,
38
67
  'tmux_session' => tmux_session,
39
68
  'tmux_window' => tmux_window,
40
69
  'tmux_pane' => tmux_pane,
41
70
  'git_branch' => git_branch,
71
+ 'git_sha' => git_sha,
72
+ 'git_origin_sha' => git_origin_sha,
42
73
  'git_worktree' => git_worktree,
43
74
  'task' => task,
44
75
  'subtask' => subtask,
76
+ 'app' => app,
45
77
  }.compact
46
78
  end
47
79
 
80
+ def state_fingerprint
81
+ Digest::SHA256.hexdigest(state_key.to_yaml)[0..15]
82
+ end
83
+
48
84
  def short_line(index)
49
- time_str = timestamp ? Time.parse(timestamp).strftime('%Y-%m-%d %H:%M') : Time.now.iso8601
85
+ time_str = timestamp ? Time.parse(timestamp).strftime('%Y-%m-%d %H:%M') : Time.now.strftime('%Y-%m-%d %H:%M')
50
86
  desc = description || cmd || '(no description)'
51
- desc = desc[0..60] + '...' if desc.length > 63
87
+ desc = desc[0..50] + '...' if desc.length > 53
52
88
  [
53
89
  format('%3d %s %s', index, time_str, desc),
54
- format(' cmd: %s', cmd),
90
+ format(' %s @ %s', git_branch || '(no branch)', task || '(no task)'),
55
91
  ].join("\n")
56
92
  end
57
93
 
94
+ def oneline(index = nil)
95
+ time_str = timestamp ? Time.parse(timestamp).strftime('%m/%d %H:%M') : ''
96
+ prefix = index ? format('%3d ', index) : ''
97
+ branch_str = git_branch ? "[#{git_branch}]" : ''
98
+ task_str = task ? "(#{task})" : ''
99
+ cmd_str = cmd || description || ''
100
+ cmd_str = cmd_str[0..40] + '...' if cmd_str.length > 43
101
+
102
+ "#{prefix}#{time_str} #{branch_str.ljust(20)} #{task_str.ljust(15)} #{cmd_str}"
103
+ end
104
+
58
105
  def full_display
59
106
  lines = []
60
107
  lines << "ID: #{id}"
@@ -70,14 +117,28 @@ class Hiiro
70
117
  lines << " Pane: #{tmux_pane}" if tmux_pane
71
118
  lines << ""
72
119
  lines << "Git:"
73
- lines << " Branch: #{git_branch}" if git_branch
74
120
  lines << " Worktree: #{git_worktree}" if git_worktree
121
+ lines << " Branch: #{git_branch}" if git_branch
122
+ lines << " SHA: #{git_sha}" if git_sha
123
+ lines << " Origin: #{git_origin_sha}" if git_origin_sha
75
124
  lines << ""
76
125
  lines << "Task:"
77
126
  lines << " Task: #{task}" if task
78
127
  lines << " Subtask: #{subtask}" if subtask
128
+ lines << " App: #{app}" if app
79
129
  lines.join("\n")
80
130
  end
131
+
132
+ # Filter matching
133
+ def matches?(filters)
134
+ filters.all? do |key, value|
135
+ entry_value = send(key) rescue nil
136
+ next true if value.nil?
137
+ next entry_value == value if value.is_a?(String)
138
+ next value.include?(entry_value) if value.is_a?(Array)
139
+ true
140
+ end
141
+ end
81
142
  end
82
143
 
83
144
  class << self
@@ -88,21 +149,35 @@ class Hiiro
88
149
  subcmd, *rest = args
89
150
  case subcmd
90
151
  when 'ls', 'list', nil
91
- history.list
152
+ history.list(limit: 20)
153
+ when 'all'
154
+ history.list(limit: nil)
92
155
  when 'show'
93
156
  history.show(rest.first)
94
157
  when 'goto'
95
158
  history.goto(rest.first)
96
159
  when 'add'
97
160
  history.add_manual(rest.join(' '), hiiro: hiiro)
161
+ when 'by'
162
+ # h history by pane|window|session|task|branch [value]
163
+ dimension, value = rest
164
+ history.list_by(dimension, value)
165
+ when 'clear'
166
+ history.clear!
167
+ puts "History cleared."
98
168
  else
99
169
  puts "Unknown history subcommand: #{subcmd}"
100
- puts "Available: ls, show <id>, goto <id>, add <description>"
170
+ puts "Available: ls, all, show <id>, goto <id>, add <description>, by <dimension> [value], clear"
101
171
  false
102
172
  end
103
173
  end
104
174
  end
105
175
 
176
+ # Called automatically when a command runs (if in a task context)
177
+ def track(cmd:, hiiro: nil)
178
+ new.track_command(cmd: cmd, hiiro: hiiro)
179
+ end
180
+
106
181
  def log(description: nil, pwd: Dir.pwd, cmd: nil, source: nil, task: nil, subtask: nil)
107
182
  new.add(
108
183
  description: description,
@@ -113,10 +188,59 @@ class Hiiro
113
188
  subtask: subtask
114
189
  )
115
190
  end
191
+
192
+ # Query helpers for other commands (e.g., h pane history)
193
+ def by_pane(pane_id = nil)
194
+ pane_id ||= current_tmux_pane
195
+ new.filter(tmux_pane: pane_id)
196
+ end
197
+
198
+ def by_window(window_id = nil)
199
+ window_id ||= current_tmux_window
200
+ new.filter(tmux_window: window_id)
201
+ end
202
+
203
+ def by_session(session_name = nil)
204
+ session_name ||= current_tmux_session
205
+ new.filter(tmux_session: session_name)
206
+ end
207
+
208
+ def by_task(task_name)
209
+ new.filter(task: task_name)
210
+ end
211
+
212
+ def by_branch(branch_name)
213
+ new.filter(git_branch: branch_name)
214
+ end
215
+
216
+ def by_worktree(worktree_path)
217
+ new.filter(git_worktree: worktree_path)
218
+ end
219
+
220
+ def by_app(app_name)
221
+ new.filter(app: app_name)
222
+ end
223
+
224
+ private
225
+
226
+ def current_tmux_pane
227
+ return nil unless ENV['TMUX']
228
+ `tmux display-message -p '#D'`.strip rescue nil
229
+ end
230
+
231
+ def current_tmux_window
232
+ return nil unless ENV['TMUX']
233
+ `tmux display-message -p '#S:#I'`.strip rescue nil
234
+ end
235
+
236
+ def current_tmux_session
237
+ return nil unless ENV['TMUX']
238
+ `tmux display-message -p '#S'`.strip rescue nil
239
+ end
116
240
  end
117
241
 
118
242
  def initialize
119
- ensure_history_file
243
+ ensure_history_dir
120
244
  end
121
245
 
122
246
  def entries
@@ -128,14 +252,63 @@ class Hiiro
128
252
  entries
129
253
  end
130
254
 
131
- def list
132
- if entries.empty?
255
+ def filter(filters = {})
256
+ entries.select { |e| e.matches?(filters) }
257
+ end
258
+
259
+ def list(limit: 20)
260
+ items = entries.reverse
261
+ items = items.first(limit) if limit
262
+
263
+ if items.empty?
133
264
  puts "No history entries."
134
265
  return true
135
266
  end
136
267
 
137
- entries.each_with_index do |entry, idx|
138
- puts entry.short_line(idx + 1)
268
+ items.reverse.each_with_index do |entry, idx|
269
+ puts entry.oneline(idx + 1)
270
+ end
271
+ true
272
+ end
273
+
274
+ def list_by(dimension, value = nil)
275
+ dimension_map = {
276
+ 'pane' => :tmux_pane,
277
+ 'window' => :tmux_window,
278
+ 'session' => :tmux_session,
279
+ 'task' => :task,
280
+ 'subtask' => :subtask,
281
+ 'branch' => :git_branch,
282
+ 'worktree' => :git_worktree,
283
+ 'app' => :app,
284
+ }
285
+
286
+ field = dimension_map[dimension]
287
+ unless field
288
+ puts "Unknown dimension: #{dimension}"
289
+ puts "Available: #{dimension_map.keys.join(', ')}"
290
+ return false
291
+ end
292
+
293
+ # If no value specified, use current context
294
+ value ||= current_value_for(field)
295
+
296
+ if value.nil?
297
+ puts "No current #{dimension} detected. Please specify a value."
298
+ return false
299
+ end
300
+
301
+ filtered = filter(field => value)
302
+
303
+ if filtered.empty?
304
+ puts "No history for #{dimension}=#{value}"
305
+ return true
306
+ end
307
+
308
+ puts "History for #{dimension}: #{value}"
309
+ puts
310
+ filtered.last(20).each_with_index do |entry, idx|
311
+ puts entry.oneline(idx + 1)
139
312
  end
140
313
  true
141
314
  end
@@ -174,26 +347,49 @@ class Hiiro
174
347
  add(
175
348
  description: description,
176
349
  source: 'manual',
177
- cmd: hiiro.full_command,
350
+ cmd: hiiro&.full_command,
178
351
  )
179
352
  true
180
353
  end
181
354
 
182
- def add(description: nil, cmd: nil, source: nil, task: nil, subtask: nil, pwd: Dir.pwd)
355
+ # Main tracking method - gathers all context and saves if changed
356
+ def track_command(cmd:, hiiro: nil, force: false)
357
+ context = gather_context
358
+ return nil unless context[:task] || force # Only track if in a task context (unless forced)
359
+
360
+ context[:cmd] = cmd
361
+ context[:source] = 'auto'
362
+
363
+ # Check if state changed
364
+ unless force
365
+ new_state = build_state_key(context)
366
+ return nil if state_unchanged?(new_state)
367
+ save_last_state(new_state)
368
+ end
369
+
370
+ add(**context)
371
+ end
372
+
373
+ def add(description: nil, cmd: nil, source: nil, task: nil, subtask: nil, pwd: nil, app: nil)
374
+ context = gather_context
375
+
183
376
  entry_data = {
184
377
  'id' => generate_id,
185
378
  'timestamp' => Time.now.iso8601,
186
379
  'description' => description,
187
380
  'cmd' => cmd,
188
- 'pwd' => pwd,
381
+ 'pwd' => pwd || context[:pwd],
189
382
  'source' => source,
190
- 'tmux_session' => current_tmux_session,
191
- 'tmux_window' => current_tmux_window,
192
- 'tmux_pane' => current_tmux_pane,
193
- 'git_branch' => current_git_branch,
194
- 'git_worktree' => current_git_worktree,
195
- 'task' => task,
196
- 'subtask' => subtask,
383
+ 'tmux_session' => context[:tmux_session],
384
+ 'tmux_window' => context[:tmux_window],
385
+ 'tmux_pane' => context[:tmux_pane],
386
+ 'git_branch' => context[:git_branch],
387
+ 'git_sha' => context[:git_sha],
388
+ 'git_origin_sha' => context[:git_origin_sha],
389
+ 'git_worktree' => context[:git_worktree],
390
+ 'task' => task || context[:task],
391
+ 'subtask' => subtask || context[:subtask],
392
+ 'app' => app || context[:app],
197
393
  }.compact
198
394
 
199
395
  all = load_raw_entries
@@ -203,14 +399,60 @@ class Hiiro
203
399
  Entry.new(entry_data)
204
400
  end
205
401
 
402
+ def clear!
403
+ save_entries([])
404
+ File.delete(LAST_STATE_FILE) if File.exist?(LAST_STATE_FILE)
405
+ @entries = nil
406
+ end
407
+
206
408
  private
207
409
 
410
+ def gather_context
411
+ {
412
+ pwd: Dir.pwd,
413
+ tmux_session: current_tmux_session,
414
+ tmux_window: current_tmux_window,
415
+ tmux_pane: current_tmux_pane,
416
+ git_branch: current_git_branch,
417
+ git_sha: current_git_sha,
418
+ git_origin_sha: current_git_origin_sha,
419
+ git_worktree: current_git_worktree,
420
+ task: current_task_name,
421
+ subtask: current_subtask_name,
422
+ app: current_app_name,
423
+ }
424
+ end
425
+
426
+ def build_state_key(context)
427
+ context.slice(
428
+ :pwd, :tmux_session, :tmux_window, :tmux_pane,
429
+ :git_branch, :git_sha, :git_origin_sha, :git_worktree,
430
+ :task, :subtask, :app
431
+ ).compact
432
+ end
433
+
434
+ def state_unchanged?(new_state)
435
+ return false unless File.exist?(LAST_STATE_FILE)
436
+ last_state = YAML.load_file(LAST_STATE_FILE) rescue {}
437
+ last_state == new_state.transform_keys(&:to_s)
438
+ end
439
+
440
+ def save_last_state(state)
441
+ File.write(LAST_STATE_FILE, state.transform_keys(&:to_s).to_yaml)
442
+ end
443
+
444
+ def current_value_for(field)
445
+ context = gather_context
446
+ context[field]
447
+ end
448
+
208
449
  def find_entry(ref)
209
450
  return nil unless ref
210
451
 
211
452
  if ref.to_s.match?(/^\d+$/)
212
453
  idx = ref.to_i - 1
213
- return entries[idx] if idx >= 0 && idx < entries.length
454
+ reversed = entries.reverse
455
+ return reversed[idx] if idx >= 0 && idx < reversed.length
214
456
  end
215
457
 
216
458
  entries.find { |e| e.id == ref.to_s }
@@ -220,9 +462,8 @@ class Hiiro
220
462
  Time.now.strftime('%Y%m%d%H%M%S') + '-' + rand(10000).to_s.rjust(4, '0')
221
463
  end
222
464
 
223
- def ensure_history_file
224
- dir = File.dirname(HISTORY_FILE)
225
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
465
+ def ensure_history_dir
466
+ FileUtils.mkdir_p(HISTORY_DIR) unless Dir.exist?(HISTORY_DIR)
226
467
 
227
468
  unless File.exist?(HISTORY_FILE)
228
469
  File.write(HISTORY_FILE, [].to_yaml)
@@ -240,9 +481,12 @@ class Hiiro
240
481
  end
241
482
 
242
483
  def save_entries(entries_data)
484
+ # Keep only last 1000 entries to prevent unbounded growth
485
+ entries_data = entries_data.last(1000) if entries_data.length > 1000
243
486
  File.write(HISTORY_FILE, entries_data.to_yaml)
244
487
  end
245
488
 
489
+ # Tmux context
246
490
  def current_tmux_session
247
491
  return nil unless ENV['TMUX']
248
492
  `tmux display-message -p '#S'`.strip rescue nil
@@ -255,13 +499,24 @@ class Hiiro
255
499
 
256
500
  def current_tmux_pane
257
501
  return nil unless ENV['TMUX']
258
- `tmux display-message -p '#P'`.strip rescue nil
502
+ ENV['TMUX_PANE'] || `tmux display-message -p '#P'`.strip rescue nil
259
503
  end
260
504
 
505
+ # Git context
261
506
  def current_git_branch
262
507
  git_helper.branch_current
263
508
  end
264
509
 
510
+ def current_git_sha
511
+ git_helper.commit('HEAD', short: true)
512
+ end
513
+
514
+ def current_git_origin_sha
515
+ branch = current_git_branch
516
+ return nil unless branch
517
+ git_helper.commit("origin/#{branch}", short: true)
518
+ end
519
+
265
520
  def current_git_worktree
266
521
  git_helper.root
267
522
  end
@@ -269,5 +524,57 @@ class Hiiro
269
524
  def git_helper
270
525
  @git_helper ||= Git.new(nil, Dir.pwd)
271
526
  end
527
+
528
+ # Task context
529
+ def current_task_name
530
+ env = environment
531
+ return nil unless env
532
+
533
+ task = env.task
534
+ return nil unless task
535
+
536
+ task.subtask? ? task.parent_name : task.name
537
+ end
538
+
539
+ def current_subtask_name
540
+ env = environment
541
+ return nil unless env
542
+
543
+ task = env.task
544
+ return nil unless task
545
+ return nil unless task.subtask?
546
+
547
+ task.short_name
548
+ end
549
+
550
+ def current_app_name
551
+ env = environment
552
+ return nil unless env
553
+
554
+ pwd = Dir.pwd
555
+ task = env.task
556
+ return nil unless task
557
+
558
+ tree = env.find_tree(task.tree_name)
559
+ return nil unless tree
560
+
561
+ # Check if pwd is in an app directory
562
+ env.all_apps.each do |app|
563
+ app_path = app.resolve(tree.path)
564
+ if pwd == app_path || pwd.start_with?(app_path + '/')
565
+ return app.name
566
+ end
567
+ end
568
+
569
+ nil
570
+ end
571
+
572
+ def environment
573
+ @environment ||= begin
574
+ Environment.current
575
+ rescue NameError
576
+ nil
577
+ end
578
+ end
272
579
  end
273
580
  end
data/lib/hiiro/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Hiiro
2
- VERSION = "0.1.51"
2
+ VERSION = "0.1.52"
3
3
  end
data/lib/hiiro.rb CHANGED
@@ -91,9 +91,11 @@ class Hiiro
91
91
  end
92
92
 
93
93
  def run
94
- log_event('hiiro: ran command')
95
94
  result = runner.run(*args)
96
95
 
96
+ # Track command after running (only saves if state changed and in task context)
97
+ History.track(cmd: full_command, hiiro: self)
98
+
97
99
  handle_result(result)
98
100
 
99
101
  exit 1
@@ -103,10 +105,6 @@ class Hiiro
103
105
  exit 1
104
106
  end
105
107
 
106
- def log_event(message)
107
- history.add_manual(message || 'command ran', hiiro: self)
108
- end
109
-
110
108
  def handle_result(result)
111
109
  exit 0 if result.nil? || result
112
110
 
data/script/update ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ gem install hiiro
4
+
5
+ h setup
6
+
7
+
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.51
4
+ version: 0.1.52
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Toyota
@@ -90,6 +90,7 @@ files:
90
90
  - script/install
91
91
  - script/publish
92
92
  - script/sync
93
+ - script/update
93
94
  homepage: https://github.com/unixsuperhero/hiiro
94
95
  licenses:
95
96
  - MIT