hcom 0.1.3__tar.gz → 0.1.4__tar.gz

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.

Potentially problematic release.


This version of hcom might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hcom
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Lightweight CLI tool for real-time messaging between Claude Code subagents using hooks
5
5
  Author-email: aannoo <your@email.com>
6
6
  License: MIT
@@ -149,9 +149,9 @@ HCOM_INSTANCE_HINTS="always update chat with progress" hcom open nice-subagent-b
149
149
 
150
150
  | Setting | Default | Environment Variable | Description |
151
151
  |---------|---------|---------------------|-------------|
152
- | `wait_timeout` | 600 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
152
+ | `wait_timeout` | 1800 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
153
153
  | `max_message_size` | 4096 | `HCOM_MAX_MESSAGE_SIZE` | Maximum message length |
154
- | `max_messages_per_delivery` | 20 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
154
+ | `max_messages_per_delivery` | 50 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
155
155
  | `sender_name` | "bigboss" | `HCOM_SENDER_NAME` | Your name in chat |
156
156
  | `sender_emoji` | "🐳" | `HCOM_SENDER_EMOJI` | Your emoji icon |
157
157
  | `initial_prompt` | "Say hi" | `HCOM_INITIAL_PROMPT` | What new instances are told to do |
@@ -121,9 +121,9 @@ HCOM_INSTANCE_HINTS="always update chat with progress" hcom open nice-subagent-b
121
121
 
122
122
  | Setting | Default | Environment Variable | Description |
123
123
  |---------|---------|---------------------|-------------|
124
- | `wait_timeout` | 600 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
124
+ | `wait_timeout` | 1800 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
125
125
  | `max_message_size` | 4096 | `HCOM_MAX_MESSAGE_SIZE` | Maximum message length |
126
- | `max_messages_per_delivery` | 20 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
126
+ | `max_messages_per_delivery` | 50 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
127
127
  | `sender_name` | "bigboss" | `HCOM_SENDER_NAME` | Your name in chat |
128
128
  | `sender_emoji` | "🐳" | `HCOM_SENDER_EMOJI` | Your emoji icon |
129
129
  | `initial_prompt` | "Say hi" | `HCOM_INITIAL_PROMPT` | What new instances are told to do |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hcom"
7
- version = "0.1.3"
7
+ version = "0.1.4"
8
8
  description = "Lightweight CLI tool for real-time messaging between Claude Code subagents using hooks"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.6"
@@ -25,7 +25,6 @@ IS_WINDOWS = sys.platform == 'win32'
25
25
  HCOM_ACTIVE_ENV = 'HCOM_ACTIVE'
26
26
  HCOM_ACTIVE_VALUE = '1'
27
27
 
28
-
29
28
  EXIT_SUCCESS = 0
30
29
  EXIT_ERROR = 1
31
30
  EXIT_BLOCK = 2
@@ -68,9 +67,9 @@ DEFAULT_CONFIG = {
68
67
  "sender_name": "bigboss",
69
68
  "sender_emoji": "🐳",
70
69
  "cli_hints": "",
71
- "wait_timeout": 600,
70
+ "wait_timeout": 1800,
72
71
  "max_message_size": 4096,
73
- "max_messages_per_delivery": 20,
72
+ "max_messages_per_delivery": 50,
74
73
  "first_use_text": "Essential, concise messages only, say hi in hcom chat now",
75
74
  "instance_hints": "",
76
75
  "env_overrides": {}
@@ -170,6 +169,29 @@ def get_config_value(key, default=None):
170
169
  config = get_cached_config()
171
170
  return config.get(key, default)
172
171
 
172
+ def get_hook_command():
173
+ """Determine the best hook command approach based on paths"""
174
+ python_path = sys.executable
175
+ script_path = os.path.abspath(__file__)
176
+
177
+ # Characters that cause issues in shell expansion
178
+ problematic_chars = [' ', '`', '"', "'", '\\', '\n', ';', '&', '|', '(', ')', ':',
179
+ '$', '*', '?', '[', ']', '{', '}', '!', '~', '<', '>']
180
+
181
+ has_problematic_chars = any(char in python_path or char in script_path
182
+ for char in problematic_chars)
183
+
184
+ if has_problematic_chars:
185
+ # Use direct paths with proper escaping
186
+ # Escape backslashes first, then quotes
187
+ escaped_python = python_path.replace('\\', '\\\\').replace('"', '\\"')
188
+ escaped_script = script_path.replace('\\', '\\\\').replace('"', '\\"')
189
+ return f'"{escaped_python}" "{escaped_script}"', {}
190
+ else:
191
+ # Use single clean env var
192
+ env_vars = {'HCOM': f'{python_path} {script_path}'}
193
+ return '$HCOM', env_vars
194
+
173
195
  def build_claude_env():
174
196
  """Build environment variables for Claude instances"""
175
197
  env = {HCOM_ACTIVE_ENV: HCOM_ACTIVE_VALUE}
@@ -184,6 +206,10 @@ def build_claude_env():
184
206
 
185
207
  env.update(config.get('env_overrides', {}))
186
208
 
209
+ # Add hook-specific env vars if using that approach
210
+ _, hook_env_vars = get_hook_command()
211
+ env.update(hook_env_vars)
212
+
187
213
  return env
188
214
 
189
215
  # ==================== Message System ====================
@@ -363,6 +389,22 @@ def _remove_hcom_hooks_from_settings(settings):
363
389
  if 'hooks' not in settings:
364
390
  return
365
391
 
392
+ import re
393
+
394
+ # Patterns to match any hcom hook command
395
+ # - $HCOM post/stop/notify
396
+ # - hcom post/stop/notify
397
+ # - /path/to/hcom.py post/stop/notify
398
+ # - "/path with spaces/python" "/path with spaces/hcom.py" post/stop/notify
399
+ # - '/path/to/python' '/path/to/hcom.py' post/stop/notify
400
+ hcom_patterns = [
401
+ r'\$HCOM\s+(post|stop|notify)\b', # Environment variable
402
+ r'\bhcom\s+(post|stop|notify)\b', # Direct hcom command
403
+ r'hcom\.py["\']?\s+(post|stop|notify)\b', # hcom.py with optional quote
404
+ r'["\'][^"\']*hcom\.py["\']?\s+(post|stop|notify)\b', # Quoted path with hcom.py
405
+ ]
406
+ compiled_patterns = [re.compile(pattern) for pattern in hcom_patterns]
407
+
366
408
  for event in ['PostToolUse', 'Stop', 'Notification']:
367
409
  if event not in settings['hooks']:
368
410
  continue
@@ -370,11 +412,12 @@ def _remove_hcom_hooks_from_settings(settings):
370
412
  settings['hooks'][event] = [
371
413
  matcher for matcher in settings['hooks'][event]
372
414
  if not any(
373
- any(pattern in hook.get('command', '')
374
- for pattern in ['hcom post', 'hcom stop', 'hcom notify',
375
- 'hcom.py post', 'hcom.py stop', 'hcom.py notify',
376
- 'hcom.py" post', 'hcom.py" stop', 'hcom.py" notify'])
377
- for hook in matcher.get('hooks', []))
415
+ any(
416
+ pattern.search(hook.get('command', ''))
417
+ for pattern in compiled_patterns
418
+ )
419
+ for hook in matcher.get('hooks', [])
420
+ )
378
421
  ]
379
422
 
380
423
  if not settings['hooks'][event]:
@@ -619,20 +662,8 @@ def setup_hooks():
619
662
  if hcom_send_permission not in settings['permissions']['allow']:
620
663
  settings['permissions']['allow'].append(hcom_send_permission)
621
664
 
622
- # Detect hcom executable path
623
- try:
624
- import subprocess
625
- # First, try regular hcom command
626
- subprocess.run(['hcom', 'help'], capture_output=True, check=True, timeout=5)
627
- hcom_cmd = 'hcom'
628
- except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
629
- try:
630
- # Try uvx hcom
631
- subprocess.run(['uvx', 'hcom', 'help'], capture_output=True, check=True, timeout=10)
632
- hcom_cmd = 'uvx hcom'
633
- except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
634
- # Last resort: use full path
635
- hcom_cmd = f'"{sys.executable}" "{os.path.abspath(__file__)}"'
665
+ # Get the hook command approach (env vars or direct paths based on spaces)
666
+ hook_cmd_base, _ = get_hook_command()
636
667
 
637
668
  # Add PostToolUse hook
638
669
  if 'PostToolUse' not in settings['hooks']:
@@ -642,7 +673,7 @@ def setup_hooks():
642
673
  'matcher': '.*',
643
674
  'hooks': [{
644
675
  'type': 'command',
645
- 'command': f'{hcom_cmd} post'
676
+ 'command': f'{hook_cmd_base} post'
646
677
  }]
647
678
  })
648
679
 
@@ -650,13 +681,13 @@ def setup_hooks():
650
681
  if 'Stop' not in settings['hooks']:
651
682
  settings['hooks']['Stop'] = []
652
683
 
653
- wait_timeout = get_config_value('wait_timeout', 600)
684
+ wait_timeout = get_config_value('wait_timeout', 1800)
654
685
 
655
686
  settings['hooks']['Stop'].append({
656
687
  'matcher': '',
657
688
  'hooks': [{
658
689
  'type': 'command',
659
- 'command': f'{hcom_cmd} stop',
690
+ 'command': f'{hook_cmd_base} stop',
660
691
  'timeout': wait_timeout
661
692
  }]
662
693
  })
@@ -669,7 +700,7 @@ def setup_hooks():
669
700
  'matcher': '',
670
701
  'hooks': [{
671
702
  'type': 'command',
672
- 'command': f'{hcom_cmd} notify'
703
+ 'command': f'{hook_cmd_base} notify'
673
704
  }]
674
705
  })
675
706
 
@@ -846,7 +877,7 @@ def get_transcript_status(transcript_path):
846
877
  def get_instance_status(pos_data):
847
878
  """Get current status of instance"""
848
879
  now = int(time.time())
849
- wait_timeout = get_config_value('wait_timeout', 600)
880
+ wait_timeout = get_config_value('wait_timeout', 1800)
850
881
 
851
882
  last_permission = pos_data.get("last_permission_request", 0)
852
883
  last_stop = pos_data.get("last_stop", 0)
@@ -1377,7 +1408,7 @@ def cmd_watch(*args):
1377
1408
 
1378
1409
  # Interactive dashboard mode
1379
1410
  last_pos = 0
1380
- status_suffix = f"{DIM} [⏎] chat...{RESET}"
1411
+ status_suffix = f"{DIM} [⏎]...{RESET}"
1381
1412
 
1382
1413
  all_messages = show_main_screen_header()
1383
1414
 
@@ -1575,6 +1606,7 @@ def cleanup_directory_hooks(directory):
1575
1606
  except Exception as e:
1576
1607
  return 1, format_error(f"Cannot modify settings.local.json: {e}")
1577
1608
 
1609
+
1578
1610
  def cmd_cleanup(*args):
1579
1611
  """Remove hcom hooks from current directory or all directories"""
1580
1612
  if args and args[0] == '--all':
@@ -1683,6 +1715,21 @@ def cmd_send(message):
1683
1715
 
1684
1716
  # ==================== Hook Functions ====================
1685
1717
 
1718
+ def format_hook_messages(messages, instance_name):
1719
+ """Format messages for hook feedback"""
1720
+ if len(messages) == 1:
1721
+ msg = messages[0]
1722
+ reason = f"{msg['from']} → {instance_name}: {msg['message']}"
1723
+ else:
1724
+ parts = [f"{msg['from']}: {msg['message']}" for msg in messages]
1725
+ reason = f"{len(messages)} messages → {instance_name}: " + " | ".join(parts)
1726
+
1727
+ instance_hints = get_config_value('instance_hints', '')
1728
+ if instance_hints:
1729
+ reason = f"{reason} {instance_hints}"
1730
+
1731
+ return reason
1732
+
1686
1733
  def handle_hook_post():
1687
1734
  """Handle PostToolUse hook"""
1688
1735
  # Check if active
@@ -1713,7 +1760,8 @@ def handle_hook_post():
1713
1760
  'directory': str(Path.cwd())
1714
1761
  })
1715
1762
 
1716
- # Check for HCOM_SEND in Bash commands
1763
+ # Check for HCOM_SEND in Bash commands
1764
+ sent_reason = None
1717
1765
  if hook_data.get('tool_name') == 'Bash':
1718
1766
  command = hook_data.get('tool_input', {}).get('command', '')
1719
1767
  if 'HCOM_SEND:' in command:
@@ -1749,21 +1797,27 @@ def handle_hook_post():
1749
1797
  sys.exit(EXIT_BLOCK)
1750
1798
 
1751
1799
  send_message(instance_name, message)
1800
+ sent_reason = "✓ Sent"
1752
1801
 
1753
1802
  # Check for pending messages to deliver
1754
1803
  if not instance_name.endswith("claude"):
1755
1804
  messages = get_new_messages(instance_name)
1756
1805
 
1757
- if messages:
1758
- # Deliver messages via exit code 2
1759
- max_messages = get_config_value('max_messages_per_delivery', 20)
1760
- messages_to_show = messages[:max_messages]
1761
-
1762
- output = {
1763
- "decision": HOOK_DECISION_BLOCK,
1764
- "reason": f"New messages from hcom:\n" +
1765
- "\n".join([f"{m['from']}: {m['message']}" for m in messages_to_show])
1766
- }
1806
+ if messages and sent_reason:
1807
+ # Both sent and received
1808
+ reason = f"{sent_reason} | {format_hook_messages(messages, instance_name)}"
1809
+ output = {"decision": HOOK_DECISION_BLOCK, "reason": reason}
1810
+ print(json.dumps(output, ensure_ascii=False), file=sys.stderr)
1811
+ sys.exit(EXIT_BLOCK)
1812
+ elif messages:
1813
+ # Just received
1814
+ reason = format_hook_messages(messages, instance_name)
1815
+ output = {"decision": HOOK_DECISION_BLOCK, "reason": reason}
1816
+ print(json.dumps(output, ensure_ascii=False), file=sys.stderr)
1817
+ sys.exit(EXIT_BLOCK)
1818
+ elif sent_reason:
1819
+ # Just sent
1820
+ output = {"reason": sent_reason}
1767
1821
  print(json.dumps(output, ensure_ascii=False), file=sys.stderr)
1768
1822
  sys.exit(EXIT_BLOCK)
1769
1823
 
@@ -1806,7 +1860,7 @@ def handle_hook_stop():
1806
1860
  check_and_show_first_use_help(instance_name)
1807
1861
 
1808
1862
  # Simple polling loop with parent check
1809
- timeout = get_config_value('wait_timeout', 600)
1863
+ timeout = get_config_value('wait_timeout', 1800)
1810
1864
  start_time = time.time()
1811
1865
 
1812
1866
  while time.time() - start_time < timeout:
@@ -1819,14 +1873,11 @@ def handle_hook_stop():
1819
1873
 
1820
1874
  if messages:
1821
1875
  # Deliver messages
1822
- max_messages = get_config_value('max_messages_per_delivery', 20)
1876
+ max_messages = get_config_value('max_messages_per_delivery', 50)
1823
1877
  messages_to_show = messages[:max_messages]
1824
1878
 
1825
- output = {
1826
- "decision": HOOK_DECISION_BLOCK,
1827
- "reason": f"New messages from hcom:\n" +
1828
- "\n".join([f"{m['from']}: {m['message']}" for m in messages_to_show])
1829
- }
1879
+ reason = format_hook_messages(messages_to_show, instance_name)
1880
+ output = {"decision": HOOK_DECISION_BLOCK, "reason": reason}
1830
1881
  print(json.dumps(output, ensure_ascii=False), file=sys.stderr)
1831
1882
  sys.exit(EXIT_BLOCK)
1832
1883
 
@@ -1918,6 +1969,7 @@ def main(argv=None):
1918
1969
  handle_hook_notification()
1919
1970
  return 0
1920
1971
 
1972
+
1921
1973
  # Unknown command
1922
1974
  else:
1923
1975
  print(format_error(f"Unknown command: {cmd}", "Run 'hcom help' for available commands"), file=sys.stderr)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hcom
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: Lightweight CLI tool for real-time messaging between Claude Code subagents using hooks
5
5
  Author-email: aannoo <your@email.com>
6
6
  License: MIT
@@ -149,9 +149,9 @@ HCOM_INSTANCE_HINTS="always update chat with progress" hcom open nice-subagent-b
149
149
 
150
150
  | Setting | Default | Environment Variable | Description |
151
151
  |---------|---------|---------------------|-------------|
152
- | `wait_timeout` | 600 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
152
+ | `wait_timeout` | 1800 | `HCOM_WAIT_TIMEOUT` | How long instances wait for messages (seconds) |
153
153
  | `max_message_size` | 4096 | `HCOM_MAX_MESSAGE_SIZE` | Maximum message length |
154
- | `max_messages_per_delivery` | 20 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
154
+ | `max_messages_per_delivery` | 50 | `HCOM_MAX_MESSAGES_PER_DELIVERY` | Messages delivered per batch |
155
155
  | `sender_name` | "bigboss" | `HCOM_SENDER_NAME` | Your name in chat |
156
156
  | `sender_emoji` | "🐳" | `HCOM_SENDER_EMOJI` | Your emoji icon |
157
157
  | `initial_prompt` | "Say hi" | `HCOM_INITIAL_PROMPT` | What new instances are told to do |
File without changes
File without changes
File without changes
File without changes