Compare commits
2 Commits
2cb83632f9
...
xim
| Author | SHA1 | Date | |
|---|---|---|---|
| fd5fe5b328 | |||
| 4ed3f4d6ab |
@@ -653,6 +653,10 @@ Building...
|
|||||||
- **Non-blocking**: Progress tones don't interrupt speech or other functionality
|
- **Non-blocking**: Progress tones don't interrupt speech or other functionality
|
||||||
- **Configurable**: Can be enabled/disabled as needed
|
- **Configurable**: Can be enabled/disabled as needed
|
||||||
|
|
||||||
|
Fenrir detects stable progress structures rather than application-specific
|
||||||
|
status formats. Application-specific formats change too frequently to support
|
||||||
|
reliably.
|
||||||
|
|
||||||
### Usage Examples
|
### Usage Examples
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -312,6 +312,9 @@ Fenrir automatically detects and provides audio feedback for progress indicators
|
|||||||
- **Automatic**: Works with downloads, compilations, installations
|
- **Automatic**: Works with downloads, compilations, installations
|
||||||
- **Remote control**: Enable via socket commands
|
- **Remote control**: Enable via socket commands
|
||||||
|
|
||||||
|
Fenrir detects stable progress structures rather than application-specific
|
||||||
|
status formats, which change too frequently to support reliably.
|
||||||
|
|
||||||
### Spell Checking
|
### Spell Checking
|
||||||
- `Fenrir + S` - Spell check current word
|
- `Fenrir + S` - Spell check current word
|
||||||
- `Fenrir + S S` - Add word to dictionary
|
- `Fenrir + S S` - Add word to dictionary
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class command:
|
|||||||
delta_length = len(delta_text)
|
delta_length = len(delta_text)
|
||||||
if (
|
if (
|
||||||
delta_length > 200
|
delta_length > 200
|
||||||
): # Allow longer progress lines like Claude Code's status
|
): # Allow longer progress lines such as terminal status output
|
||||||
if not self.is_explicit_progress_delta(delta_text):
|
if not self.is_explicit_progress_delta(delta_text):
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
f"Progress filter: delta too long ({delta_length})",
|
f"Progress filter: delta too long ({delta_length})",
|
||||||
@@ -326,43 +326,27 @@ class command:
|
|||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 6: Claude Code working indicators (various symbols + activity text + "esc/ctrl+c to interrupt")
|
# Pattern 6: Interruptible terminal activity indicators
|
||||||
# Matches any: [symbol] [Task description]… (... to interrupt ...)
|
# Matches any: [symbol] [Task description][…] (... to interrupt ...)
|
||||||
# Symbols include: * ✢ ✽ ✶ ✻ · • ◦ ○ ● ◆ and similar decorative characters
|
# Symbols include: * ✢ ✽ ✶ ✻ · • ◦ ○ ● ◆ and similar decorative characters
|
||||||
# Example: ✽ Reviewing script for issues… (ctrl+c to interrupt · 33s · ↑ 1.6k tokens · thought for 4s)
|
# Keep this structural rather than adding application-specific formats,
|
||||||
claude_progress_match = re.search(
|
# which change too frequently to support reliably.
|
||||||
r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?…\s*\(.*(?:esc|ctrl\+c) to interrupt.*\)',
|
interruptible_activity_match = re.search(
|
||||||
|
r'[*✢✽✶✻·•◦○●◆]\s+\w+.*?(?:…\s*)?\(.*(?:esc|ctrl\+c) to interrupt.*\)',
|
||||||
text,
|
text,
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
if claude_progress_match:
|
if interruptible_activity_match:
|
||||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
self.env["runtime"]["DebugManager"].write_debug_out(
|
||||||
"Playing Claude Code activity beep",
|
"Playing interruptible activity beep",
|
||||||
debug.DebugLevel.INFO,
|
debug.DebugLevel.INFO,
|
||||||
)
|
)
|
||||||
self.play_activity_beep()
|
self.play_activity_beep()
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
||||||
return
|
return
|
||||||
|
|
||||||
# Pattern 6b: Claude Code tool invocation indicators (● Tool Name(...))
|
# Pattern 6b: Bullet/white bullet activity lines (•/◦ ...)
|
||||||
# Example: ● Web Search("query here")
|
|
||||||
tool_invocation_match = re.search(
|
|
||||||
r'[●○◉•◦]\s+(?:Web\s*Search|Read|Write|Edit|Bash|Glob|Grep|Task|WebFetch)\s*\(',
|
|
||||||
text,
|
|
||||||
re.IGNORECASE,
|
|
||||||
)
|
|
||||||
if tool_invocation_match:
|
|
||||||
if current_time - self.env["commandBuffer"]["lastProgressTime"] >= 1.0:
|
|
||||||
self.env["runtime"]["DebugManager"].write_debug_out(
|
|
||||||
"Playing Claude Code tool invocation beep",
|
|
||||||
debug.DebugLevel.INFO,
|
|
||||||
)
|
|
||||||
self.play_activity_beep()
|
|
||||||
self.env["commandBuffer"]["lastProgressTime"] = current_time
|
|
||||||
return
|
|
||||||
|
|
||||||
# Pattern 6c: Bullet/white bullet activity lines (•/◦ ...)
|
|
||||||
bullet_activity_match = re.search(
|
bullet_activity_match = re.search(
|
||||||
(
|
(
|
||||||
r'^\s*[•◦]\s+.*(?:…|\.{3,}|\b(?:thinking|working|processing|'
|
r'^\s*[•◦]\s+.*(?:…|\.{3,}|\b(?:thinking|working|processing|'
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
# Fenrir TTY screen reader
|
# Fenrir TTY screen reader
|
||||||
# By Chrys, Storm Dragon, and contributors.
|
# By Chrys, Storm Dragon, and contributors.
|
||||||
|
|
||||||
version = "2026.05.31"
|
version = "2026.06.01"
|
||||||
code_name = "testing"
|
code_name = "master"
|
||||||
|
|||||||
@@ -109,3 +109,34 @@ def test_progress_detector_beeps_for_long_tqdm_transfer_delta():
|
|||||||
|
|
||||||
command.play_progress_tone.assert_called_once_with(90.0)
|
command.play_progress_tone.assert_called_once_with(90.0)
|
||||||
assert command.env["commandBuffer"]["lastProgressValue"] == 90.0
|
assert command.env["commandBuffer"]["lastProgressValue"] == 90.0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_progress_detector_beeps_for_interruptible_status_without_ellipsis():
|
||||||
|
progress_module = _load_progress_module()
|
||||||
|
command = progress_module.command()
|
||||||
|
sample = "◦ Files: (1m 04s • esc to interrupt)"
|
||||||
|
command.env = {
|
||||||
|
"commandBuffer": {
|
||||||
|
"progress_monitoring": True,
|
||||||
|
"lastProgressValue": -1,
|
||||||
|
"lastProgressTime": 0,
|
||||||
|
},
|
||||||
|
"runtime": {
|
||||||
|
"DebugManager": Mock(write_debug_out=Mock()),
|
||||||
|
"ScreenManager": Mock(is_screen_change=Mock(return_value=False)),
|
||||||
|
"CursorManager": Mock(is_cursor_vertical_move=Mock(return_value=False)),
|
||||||
|
},
|
||||||
|
"screen": {
|
||||||
|
"new_delta": sample,
|
||||||
|
"new_delta_is_typing": False,
|
||||||
|
"new_content_text": sample,
|
||||||
|
"old_cursor": {"x": 0, "y": 0},
|
||||||
|
"new_cursor": {"x": 0, "y": 0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
command.play_activity_beep = Mock()
|
||||||
|
|
||||||
|
command.run()
|
||||||
|
|
||||||
|
command.play_activity_beep.assert_called_once_with()
|
||||||
|
|||||||
Reference in New Issue
Block a user