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.
- {hcom-0.1.3/src/hcom.egg-info → hcom-0.1.4}/PKG-INFO +3 -3
- {hcom-0.1.3 → hcom-0.1.4}/README.md +2 -2
- {hcom-0.1.3 → hcom-0.1.4}/pyproject.toml +1 -1
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom/__main__.py +98 -46
- {hcom-0.1.3 → hcom-0.1.4/src/hcom.egg-info}/PKG-INFO +3 -3
- {hcom-0.1.3 → hcom-0.1.4}/MANIFEST.in +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/setup.cfg +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom/__init__.py +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom.egg-info/SOURCES.txt +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom.egg-info/dependency_links.txt +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom.egg-info/entry_points.txt +0 -0
- {hcom-0.1.3 → hcom-0.1.4}/src/hcom.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hcom
|
|
3
|
-
Version: 0.1.
|
|
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` |
|
|
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` |
|
|
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` |
|
|
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` |
|
|
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 |
|
|
@@ -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":
|
|
70
|
+
"wait_timeout": 1800,
|
|
72
71
|
"max_message_size": 4096,
|
|
73
|
-
"max_messages_per_delivery":
|
|
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(
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
#
|
|
623
|
-
|
|
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'{
|
|
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',
|
|
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'{
|
|
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'{
|
|
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',
|
|
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} [⏎]
|
|
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
|
-
#
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
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',
|
|
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',
|
|
1876
|
+
max_messages = get_config_value('max_messages_per_delivery', 50)
|
|
1823
1877
|
messages_to_show = messages[:max_messages]
|
|
1824
1878
|
|
|
1825
|
-
|
|
1826
|
-
|
|
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
|
+
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` |
|
|
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` |
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|