Skip to content

Commit ece671b

Browse files
committed
resolve conflicts
2 parents 873b67a + 94343f0 commit ece671b

18 files changed

Lines changed: 396 additions & 84 deletions

README.ja.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ brew install --cask yugo-ibuki/tap/unitmux
6969
| `Ctrl+D` | セッション詳細ポップアップを表示(キーは設定で変更可能) |
7070
| `Ctrl+C` | セッションを終了(詳細パネル表示中、確認ダイアログあり) |
7171
| `Ctrl+G` | git操作ポップアップを表示(キーは設定で変更可能) |
72+
| `Ctrl+F` | git diffビューアーを表示(キーは設定で変更可能) |
7273
| `Ctrl+S` | セッションの実行を停止 — Escapeを送信して中断(キーは設定で変更可能) |
7374
| `Ctrl+W` | コンパクトモード切り替え — タブバーだけの細い表示に(キーは設定で変更可能) |
7475
| `Ctrl+N` | 新しいセッションを作成(tmuxセッションとコマンドを選択) |
@@ -114,6 +115,7 @@ brew install --cask yugo-ibuki/tap/unitmux
114115
- **Preview Key** — プレビュー表示のキーを変更(`Ctrl+<キー>`
115116
- **Detail Key** — セッション詳細のキーを変更(`Ctrl+<キー>`
116117
- **Git Key** — git操作のキーを変更(`Ctrl+<キー>`
118+
- **Diff Key** — git diffビューアーのキーを変更(`Ctrl+<キー>`
117119
- **Focus Key** — unitmuxにフォーカスするグローバルショートカットを変更(`Cmd+Shift+<キー>`
118120
- **Slash Commands** — スラッシュコマンドの追加・編集・削除
119121

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ When `claude` or `codex` presents numbered choices (e.g. "1. Yes / 2. No"), clic
7171
| `Ctrl+D` | Open session detail popup (key configurable) |
7272
| `Ctrl+C` | Close session with confirmation (when detail panel is open) |
7373
| `Ctrl+G` | Open git operations popup (key configurable) |
74+
| `Ctrl+F` | Open git diff viewer (key configurable) |
7475
| `Ctrl+S` | Stop the running session — sends Escape to interrupt (key configurable) |
7576
| `Ctrl+W` | Toggle compact mode — shrinks window to tab bar only (key configurable) |
7677
| `Ctrl+N` | Create a new session (select target tmux session and command) |
@@ -116,6 +117,7 @@ Click the gear icon to access settings:
116117
- **Preview Key** — change the key for pane content preview (`Ctrl+<key>`)
117118
- **Detail Key** — change the key for session detail popup (`Ctrl+<key>`)
118119
- **Git Key** — change the key for git operations popup (`Ctrl+<key>`)
120+
- **Diff Key** — change the key for git diff viewer (`Ctrl+<key>`)
119121
- **Focus Key** — change the global shortcut to focus unitmux (`Cmd+Shift+<key>`)
120122
- **Slash Commands** — add, edit, and delete reusable slash commands
121123

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import {
1919
gitDiff,
2020
getConversationLog
2121
} from './tmux'
22-
import type { ChatMessage } from './tmux'
2322

2423
interface SkillEntry {
2524
name: string
@@ -79,12 +78,19 @@ function startStream(win: BrowserWindow, target: string, mode: 'raw' | 'chat'):
7978
if (!streamTarget) return
8079
try {
8180
if (streamMode === 'chat') {
82-
const messages: ChatMessage[] = await getConversationLog(streamTarget)
81+
const [messages, content] = await Promise.all([
82+
getConversationLog(streamTarget),
83+
capturePane(streamTarget)
84+
])
8385
const json = JSON.stringify(messages)
8486
if (json !== lastChatJson) {
8587
lastChatJson = json
8688
win.webContents.send('tmux:chat-data', messages)
8789
}
90+
if (content !== lastStreamContent) {
91+
lastStreamContent = content
92+
win.webContents.send('tmux:stream-data', content)
93+
}
8894
} else {
8995
const content = await capturePane(streamTarget)
9096
if (content !== lastStreamContent) {

src/main/tmux.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface TmuxPane {
1919
status: PaneStatus
2020
choices: TmuxChoice[]
2121
prompt: string
22+
activityLine: string
2223
}
2324

2425
// Strip ANSI escape sequences from captured pane content.
@@ -515,26 +516,49 @@ const CODEX_IDLE_INDICATORS = [
515516
/\bsend\b.*\bnewline\b.*\bquit\b/ // legacy format (backward compat)
516517
]
517518

519+
// Extract activity indicator line from pane content (e.g. "✻ Imagining… (17s · ↑ 107 tokens)")
520+
// These lines appear when Claude is actively processing.
521+
function parseActivityLine(content: string): string {
522+
const lines = content.split('\n')
523+
for (let i = lines.length - 1; i >= Math.max(0, lines.length - 20); i--) {
524+
const line = lines[i].trim()
525+
if (!line) continue
526+
// Match lines starting with activity indicator characters (✻, ⏺, braille spinners)
527+
// followed by text containing ellipsis (…)
528+
if (/^[\u2800-\u28FF]/.test(line) && //.test(line)) {
529+
return line
530+
}
531+
}
532+
return ''
533+
}
534+
518535
function detectStatusClaude(
519536
title: string,
520537
content: string
521-
): { status: PaneStatus; choices: TmuxChoice[]; prompt: string } {
538+
): { status: PaneStatus; choices: TmuxChoice[]; prompt: string; activityLine: string } {
522539
// Always check for choices first — permission prompts (e.g. "Do you want to proceed?
523540
// ❯ 1. Yes 2. No") can appear while the title still shows ⠂ (busy).
524541
const choices = parseChoices(content)
525542
if (choices.length > 0) {
526-
return { status: 'waiting', choices, prompt: parsePrompt(content) }
543+
return { status: 'waiting', choices, prompt: parsePrompt(content), activityLine: '' }
527544
}
528545

529-
if (!title.includes('✳')) return { status: 'busy', choices: [], prompt: '' }
546+
if (!title.includes('✳')) {
547+
return {
548+
status: 'busy',
549+
choices: [],
550+
prompt: '',
551+
activityLine: parseActivityLine(content)
552+
}
553+
}
530554

531555
const lines = content.split('\n').slice(-10)
532556
for (const pattern of WAITING_PATTERNS) {
533557
if (lines.some((line) => pattern.test(line))) {
534-
return { status: 'waiting', choices: [], prompt: parsePrompt(content) }
558+
return { status: 'waiting', choices: [], prompt: parsePrompt(content), activityLine: '' }
535559
}
536560
}
537-
return { status: 'idle', choices: [], prompt: '' }
561+
return { status: 'idle', choices: [], prompt: '', activityLine: '' }
538562
}
539563

540564
// Codex presents options as indented "- " list items
@@ -546,14 +570,15 @@ function detectStatusCodex(content: string): {
546570
status: PaneStatus
547571
choices: TmuxChoice[]
548572
prompt: string
573+
activityLine: string
549574
} {
550575
const lines = content.split('\n').slice(-15)
551576
const tail = lines.join('\n')
552577

553578
// 1. Check for explicit busy signals (-ing words or "esc to interrupt")
554579
const isBusy = CODEX_BUSY_PATTERNS.some((p) => p.test(tail))
555580
if (isBusy) {
556-
return { status: 'busy', choices: [], prompt: '' }
581+
return { status: 'busy', choices: [], prompt: '', activityLine: parseActivityLine(content) }
557582
}
558583

559584
// 2. Check for explicit idle signals (footer hints)
@@ -562,22 +587,22 @@ function detectStatusCodex(content: string): {
562587
const hasQuestion = lines.some((line) => CODEX_QUESTION_PATTERN.test(line))
563588
const optionCount = lines.filter((line) => CODEX_OPTION_PATTERN.test(line)).length
564589
if (hasQuestion || optionCount >= 2) {
565-
return { status: 'waiting', choices: [], prompt: '' }
590+
return { status: 'waiting', choices: [], prompt: '', activityLine: '' }
566591
}
567-
return { status: 'idle', choices: [], prompt: '' }
592+
return { status: 'idle', choices: [], prompt: '', activityLine: '' }
568593
}
569594

570595
// 3. No busy signal detected → default to idle (not busy).
571596
// If Codex is NOT showing "Working"/"Thinking"/etc,
572597
// it is most likely at the input prompt waiting for user input.
573-
return { status: 'idle', choices: [], prompt: '' }
598+
return { status: 'idle', choices: [], prompt: '', activityLine: '' }
574599
}
575600

576601
function detectStatus(
577602
title: string,
578603
content: string,
579604
command: string
580-
): { status: PaneStatus; choices: TmuxChoice[]; prompt: string } {
605+
): { status: PaneStatus; choices: TmuxChoice[]; prompt: string; activityLine: string } {
581606
if (command === 'codex') return detectStatusCodex(content)
582607
return detectStatusClaude(title, content)
583608
}
@@ -600,7 +625,8 @@ export async function listPanes(): Promise<TmuxPane[]> {
600625
title,
601626
status: 'busy' as PaneStatus,
602627
choices: [] as TmuxChoice[],
603-
prompt: ''
628+
prompt: '',
629+
activityLine: ''
604630
}
605631
})
606632
// Support popular wrappers like `ai` in addition to `claude` and `codex`.
@@ -639,6 +665,7 @@ export async function listPanes(): Promise<TmuxPane[]> {
639665
pane.status = result.status
640666
pane.choices = result.choices
641667
pane.prompt = result.prompt
668+
pane.activityLine = result.activityLine
642669
})
643670
)
644671

src/preload/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ interface TmuxPane {
1818
status: 'idle' | 'busy' | 'waiting'
1919
choices: TmuxChoice[]
2020
prompt: string
21+
activityLine: string
2122
}
2223

2324
interface PaneDetail {

src/preload/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface TmuxPane {
1616
pid: string
1717
command: string
1818
title: string
19+
activityLine: string
1920
}
2021

2122
export interface PaneDetail {

0 commit comments

Comments
 (0)