@devo-bmad-custom/agent-orchestration 1.0.6 → 1.0.7

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.
Files changed (63) hide show
  1. package/package.json +4 -2
  2. package/src/.agents/skills/tmux-commands/SKILL.md +1 -1
  3. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +475 -0
  4. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +736 -0
  5. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +738 -0
  6. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +410 -0
  7. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +567 -0
  8. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +497 -0
  9. package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +871 -0
  10. package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +58 -0
  11. package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +101 -0
  12. package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +100 -0
  13. package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +31 -0
  14. package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +253 -0
  15. package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +1067 -0
  16. package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +114 -0
  17. package/src/.agents/skills/ux-audit/SKILL.md +151 -0
  18. package/src/.agents/skills/websocket-engineer/SKILL.md +168 -0
  19. package/src/.agents/skills/websocket-engineer/references/alternatives.md +391 -0
  20. package/src/.agents/skills/websocket-engineer/references/patterns.md +400 -0
  21. package/src/.agents/skills/websocket-engineer/references/protocol.md +195 -0
  22. package/src/.agents/skills/websocket-engineer/references/scaling.md +333 -0
  23. package/src/.agents/skills/websocket-engineer/references/security.md +474 -0
  24. package/src/.agents/skills/writing-skills/SKILL.md +655 -0
  25. package/src/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
  26. package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  27. package/src/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
  28. package/src/.agents/skills/writing-skills/persuasion-principles.md +187 -0
  29. package/src/.agents/skills/writing-skills/render-graphs.js +168 -0
  30. package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  31. package/src/.claude/commands/bmad-track-compact.md +19 -0
  32. package/src/.claude/commands/bmad-track-extended.md +19 -0
  33. package/src/.claude/commands/bmad-track-large.md +19 -0
  34. package/src/.claude/commands/bmad-track-medium.md +19 -0
  35. package/src/.claude/commands/bmad-track-nano.md +19 -0
  36. package/src/.claude/commands/bmad-track-rv.md +18 -0
  37. package/src/.claude/commands/bmad-track-small.md +19 -0
  38. package/src/.claude/commands/master-orchestrator.md +15 -0
  39. package/src/_memory/master-orchestrator-sidecar/docs-index.md +3 -0
  40. package/src/_memory/master-orchestrator-sidecar/instructions.md +2616 -0
  41. package/src/_memory/master-orchestrator-sidecar/memories.md +8 -0
  42. package/src/_memory/master-orchestrator-sidecar/session-state.md +15 -0
  43. package/src/_memory/master-orchestrator-sidecar/triage-history.md +3 -0
  44. package/src/_memory/master-orchestrator-sidecar/workflows-overview.html +1230 -0
  45. package/src/core/agents/master-orchestrator.md +54 -0
  46. package/src/docs/dev/tmux/actions_popup.py +291 -0
  47. package/src/docs/dev/tmux/actions_popup.sh +110 -0
  48. package/src/docs/dev/tmux/claude_usage.sh +15 -0
  49. package/src/docs/dev/tmux/colors.conf +26 -0
  50. package/src/docs/dev/tmux/cpu_usage.sh +7 -0
  51. package/src/docs/dev/tmux/dispatch.sh +10 -0
  52. package/src/docs/dev/tmux/float_init.sh +13 -0
  53. package/src/docs/dev/tmux/float_term.sh +23 -0
  54. package/src/docs/dev/tmux/open_clip.sh +14 -0
  55. package/src/docs/dev/tmux/paste_claude.sh +26 -0
  56. package/src/docs/dev/tmux/paste_clipboard.sh +13 -0
  57. package/src/docs/dev/tmux/paste_image_wrapper.sh +98 -0
  58. package/src/docs/dev/tmux/ram_usage.sh +3 -0
  59. package/src/docs/dev/tmux/title_sync.sh +54 -0
  60. package/src/docs/dev/tmux/tmux-setup.md +867 -0
  61. package/src/docs/dev/tmux/tmux-test.sh +255 -0
  62. package/src/docs/dev/tmux/tmux.conf +127 -0
  63. package/src/docs/dev/tmux/xclip +18 -0
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: "master-orchestrator"
3
+ description: "AI-native agile workflow orchestrator — triages tasks, routes to BMAD workflow tracks, gates milestones with adversarial and code quality reviews. The top-level conductor for multi-agent pipelines."
4
+ ---
5
+
6
+ You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
7
+
8
+ ```xml
9
+ <agent id="master-orchestrator.agent.yaml" name="Conductor" title="Master Orchestrator" icon="🎯" module="core" hasSidecar="true" sidecarFile="_bmad/_memory/master-orchestrator-sidecar/instructions.md" capabilities="workflow orchestration, triage, track routing, review gate coordination, session management, parallel agent coordination">
10
+ <activation>
11
+ <step n="1">Load persona from this current agent file (already in context)</step>
12
+ <step n="2" critical="true">Load {project-root}/_devo-bmad-custom/core/config.yaml. Store: {user_name}, {communication_language}, {output_folder}. HALT if config fails to load.</step>
13
+ <step n="3" critical="true">Load sidecar: {project-root}/_devo-bmad-custom/_memory/master-orchestrator-sidecar/instructions.md. This file contains all routing rules, triage logic, track definitions, and protocol specifications. HALT if not found.</step>
14
+ <step n="4">Load supporting sidecar files: memories.md, triage-history.md, docs-index.md from the same sidecar directory.</step>
15
+ <step n="5">Run Bootstrap Sequence as defined in instructions.md (silently, before greeting).</step>
16
+ <step n="6">SKILLS DETECTION (MANDATORY): Scan {project-root}/.agents/skills/ and {project-root}/_devo-bmad-custom/_memory/skills/ for all SKILL.md files.</step>
17
+ <step n="7">Display greeting per instructions.md Greeting Script (Branch A, B, or C based on session state).</step>
18
+ <step n="8">STOP and WAIT for user input — do NOT execute menu items automatically.</step>
19
+ <step n="9">On user input: Number → menu item[n] | Text → substring match | [NT] → new task triage | [RS] → resume session | Other commands → per instructions.md routing table.</step>
20
+ <step n="10">When processing a command: follow handler instructions from instructions.md exactly.</step>
21
+
22
+ <rules>
23
+ <r>ALWAYS load and follow instructions.md before taking any action. The sidecar is the source of truth for all workflow rules.</r>
24
+ <r>NEVER skip triage. If user tries to bypass: "Can't skip the three questions — one bad routing decision costs more time than triage takes."</r>
25
+ <r>ALWAYS communicate in {communication_language}.</r>
26
+ <r>NEVER auto-create branches without user confirmation. Propose, then wait for explicit confirm.</r>
27
+ <r>PIPELINE PRINCIPLE: After every gate pass, announce result in one line and proceed immediately. Do NOT halt for passing results.</r>
28
+ <r>Dev agents always launch in split pane with --dangerously-skip-permissions. No exceptions.</r>
29
+ <r>Every spawned agent MUST report back via tmux send-keys before closing.</r>
30
+ <r>Pane close sequence: send /exit first, then kill-pane. Never kill without /exit.</r>
31
+ </rules>
32
+
33
+ <menu>
34
+ <item n="1" cmd="NT">New task — triage a new feature, fix, or change request</item>
35
+ <item n="2" cmd="RS">Resume session — continue previous work by description, branch, or date</item>
36
+ <item n="3" cmd="VS">View session — show active session state and pipeline status</item>
37
+ <item n="4" cmd="LK">Lookup — search feedback/issues linked to current work</item>
38
+ <item n="5" cmd="SU">Status — show active branch, track, and current step</item>
39
+ <item n="6" cmd="RC">Refresh context — re-scan docs and update index</item>
40
+ <item n="7" cmd="SV">Save session — persist current state to sidecar</item>
41
+ <item n="8" cmd="XM">Switch execution mode — change between inline/command-blocks/scripts/teams</item>
42
+ <item n="9" cmd="TM">Merge task — combine current task with another branch</item>
43
+ <item n="10" cmd="RV">Review track — audit existing code without new feature triage</item>
44
+ <item n="11" cmd="UV">UI review — single-pass UI/UX audit</item>
45
+ <item n="12" cmd="UVL">UI review loop — N-pass autonomous UI review and fix cycle</item>
46
+ <item n="13" cmd="DRY">DRY/SOLID review — single-pass code quality audit</item>
47
+ <item n="14" cmd="DRYL">DRY/SOLID review loop — N-pass autonomous quality review cycle</item>
48
+ <item n="15" cmd="SR">Security review — single-pass security audit</item>
49
+ <item n="16" cmd="SRL">Security review loop — N-pass autonomous security review cycle</item>
50
+ <item n="17" cmd="EXIT">Exit Master Orchestrator</item>
51
+ </menu>
52
+ </activation>
53
+ </agent>
54
+ ```
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env python3
2
+ """tmux Actions — interactive multi-column grid menu."""
3
+ import os, sys, termios, re, subprocess, select
4
+
5
+ PANE = sys.argv[1] if len(sys.argv) > 1 else ""
6
+
7
+ # Catppuccin Frappe palette
8
+ HDR = '\033[1;38;2;140;170;238m'
9
+ KEY = '\033[1;38;2;229;200;144m'
10
+ TXT = '\033[0;38;2;198;208;245m'
11
+ DIM = '\033[0;38;2;65;69;89m'
12
+ SEP = '\033[0;38;2;115;121;148m'
13
+ HBG = '\033[48;2;67;70;89m'
14
+ HK = '\033[1;38;2;255;213;116m'
15
+ HT = '\033[1;38;2;220;224;255m'
16
+ RST = '\033[0m'
17
+
18
+ W = 26 # cell content width (popup must be >= W*3 + 8)
19
+
20
+ # All keys are lowercase. ("---", label) = separator. ("", "") = empty padding.
21
+ COLS = [
22
+ [("z"," Zoom / unzoom pane"),("p"," Previous window"),("n"," Next window"),
23
+ ("w"," List windows & sessions"),("a"," New window"),("x"," Kill window"),
24
+ ("s"," Rename session"),("q"," Kill session"),("d"," Detach"),
25
+ ("","")],
26
+ [("h"," Split top / bottom"),("v"," Split left / right"),("k"," Kill pane"),
27
+ (","," Rename window"),
28
+ ("---","Pane Movement"),
29
+ ("u"," Swap pane \u2190 prev"),("j"," Swap pane \u2192 next"),
30
+ ("e"," Break to new window"),("m"," Move to window\u2026"),("l"," Pull from window\u2026")],
31
+ [("f"," Float terminal"),("t"," Scratch terminal (M-i)"),
32
+ ("y"," Save session"),("r"," Restore session"),
33
+ ("---","Clipboard"),
34
+ ("c"," Copy mode"),("i"," Paste image"),
35
+ ("o"," Open URL / file"),("g"," Search in browser"),
36
+ ("","")],
37
+ ]
38
+
39
+ NR = max(len(c) for c in COLS)
40
+ NC = len(COLS)
41
+ DATA_Y0 = 5 # 1-indexed terminal row where data row 0 starts
42
+
43
+ def selectable(c, r):
44
+ if not (0 <= c < NC and 0 <= r < len(COLS[c])): return False
45
+ k, _ = COLS[c][r]
46
+ return bool(k) and k != "---"
47
+
48
+ def render_cell(c, r, hl):
49
+ if r >= len(COLS[c]): return ' ' * W
50
+ k, d = COLS[c][r]
51
+ if not k: return ' ' * W
52
+ if k == "---":
53
+ s = f"\u2500\u2500 {d} "
54
+ s += '\u2500' * max(0, W - len(s))
55
+ return f"{SEP}{s[:W]}{RST}"
56
+ dp = d + ' ' * max(0, W - 1 - len(d))
57
+ if hl: return f"{HBG}{HK}{k}{HT}{dp}{RST}"
58
+ return f"{KEY}{k}{TXT}{dp}{RST}"
59
+
60
+ def hline(l, m, r):
61
+ s = '\u2500' * (W + 2)
62
+ return f"{DIM}{l}{s}{m}{s}{m}{s}{r}{RST}\n"
63
+
64
+ def data_row_line(r, sc, sr):
65
+ cells = [render_cell(c, r, sc == c and sr == r) for c in range(NC)]
66
+ return f"{DIM}\u2502{RST} {cells[0]} {DIM}\u2502{RST} {cells[1]} {DIM}\u2502{RST} {cells[2]} {DIM}\u2502{RST}"
67
+
68
+ BSU = '\033[?2026h' # begin synchronized update (buffer, don't render yet)
69
+ ESU = '\033[?2026l' # end synchronized update (flush to screen atomically)
70
+
71
+ def draw(sc, sr):
72
+ """Full initial render."""
73
+ iw = W * 3 + 6
74
+ out = [BSU, "\033[2J\033[H",
75
+ f"{HDR} {'tmux Actions':<{iw}}{RST}\n",
76
+ hline('\u250c', '\u252c', '\u2510')]
77
+ hdrs = [f"{HDR}{h:<{W}}{RST}" for h in
78
+ [' Window & Session', ' Pane Controls', ' Tools & Clipboard']]
79
+ out.append(f"{DIM}\u2502{RST} {hdrs[0]} {DIM}\u2502{RST} {hdrs[1]} {DIM}\u2502{RST} {hdrs[2]} {DIM}\u2502{RST}\n")
80
+ out.append(hline('\u251c', '\u253c', '\u2524'))
81
+ for r in range(NR):
82
+ out.append(data_row_line(r, sc, sr) + "\n")
83
+ out.append(hline('\u2514', '\u2534', '\u2518'))
84
+ out.append(f"\n{DIM} \u2191\u2193\u2190\u2192 navigate \u00b7 Enter / click to run \u00b7 Esc cancel{RST}\n")
85
+ out.append(ESU)
86
+ sys.stdout.write(''.join(out))
87
+ sys.stdout.flush()
88
+
89
+ def update(old_sc, old_sr, sc, sr):
90
+ """Redraw only the rows whose highlight state changed."""
91
+ rows = {old_sr, sr}
92
+ out = [BSU]
93
+ for r in sorted(rows):
94
+ out.append(f"\033[{DATA_Y0 + r};1H\033[2K{data_row_line(r, sc, sr)}")
95
+ out.append(ESU)
96
+ sys.stdout.write(''.join(out))
97
+ sys.stdout.flush()
98
+
99
+ def x2col(x):
100
+ if 2 <= x <= W + 2: return 0
101
+ if W + 4 <= x <= 2*W + 4: return 1
102
+ if 2*W + 6 <= x <= 3*W + 6: return 2
103
+ return -1
104
+
105
+ def y2row(y): return y - DATA_Y0
106
+
107
+ def move(sc, sr, dc, dr):
108
+ if dr:
109
+ r = sr + dr
110
+ while 0 <= r < NR:
111
+ if selectable(sc, r): return sc, r
112
+ r += dr
113
+ return sc, sr
114
+ if dc:
115
+ c = sc + dc
116
+ while 0 <= c < NC:
117
+ if selectable(c, sr): return c, sr
118
+ best_r, best_d = sr, NR
119
+ for r in range(NR):
120
+ if selectable(c, r):
121
+ d = abs(r - sr)
122
+ if d < best_d: best_d, best_r = d, r
123
+ if best_d < NR: return c, best_r
124
+ c += dc
125
+ return sc, sr
126
+ return sc, sr
127
+
128
+ def tmux(*args, bg=False):
129
+ fn = subprocess.Popen if bg else subprocess.run
130
+ fn(['tmux'] + list(args))
131
+
132
+ def execute(key):
133
+ T = (['-t', PANE] if PANE else [])
134
+ r = subprocess.run(['tmux', 'display-message', '-p'] + (['-t', PANE] if PANE else []) +
135
+ ['#{pane_current_path}'], capture_output=True, text=True)
136
+ cwd = r.stdout.strip()
137
+ c_args = (['-c', cwd] if cwd else [])
138
+ H = os.path.expanduser('~')
139
+
140
+ dispatch = {
141
+ 'z': lambda: tmux('resize-pane', '-Z', *T),
142
+ 'p': lambda: tmux('previous-window'),
143
+ 'n': lambda: tmux('next-window'),
144
+ 'w': lambda: tmux('run-shell', '-b', "sleep 0.15 && tmux choose-tree -s"),
145
+ 'a': lambda: tmux('new-window', *c_args),
146
+ 'x': lambda: tmux('run-shell', '-b', "sleep 0.1 && tmux kill-window"),
147
+ # rename-session: lock flag prevents pane-title-changed hook from overriding manual name
148
+ 's': lambda: tmux('run-shell', '-b', "sleep 0.15 && tmux command-prompt -I '#{session_name}' \"rename-session -- '%%' \\; set-option @session-name-locked 1\""),
149
+ 'q': lambda: tmux('run-shell', '-b', "sleep 0.1 && tmux kill-session"),
150
+ 'd': lambda: tmux('detach-client'),
151
+ 'h': lambda: tmux('split-window', '-v', *c_args),
152
+ 'v': lambda: tmux('split-window', '-h', *c_args),
153
+ 'k': lambda: tmux('run-shell', '-b', f"sleep 0.1 && tmux kill-pane{' -t ' + PANE if PANE else ''}"),
154
+ # rename-window: lock flag prevents pane-title-changed hook from overriding manual name
155
+ ',': lambda: tmux('run-shell', '-b', "sleep 0.15 && tmux command-prompt -I '#W' \"rename-window -- '%%' \\; set-option -w @window-name-locked 1\""),
156
+ 'u': lambda: tmux('swap-pane', '-U', *T),
157
+ 'j': lambda: tmux('swap-pane', '-D', *T),
158
+ 'e': lambda: tmux('break-pane', *T),
159
+ 'm': lambda: tmux('run-shell', '-b', "sleep 0.2 && tmux choose-tree -w 'join-pane -t \"%%\"'"),
160
+ 'l': lambda: tmux('run-shell', '-b', "sleep 0.2 && tmux choose-tree -w 'join-pane -s \"%%\"'"),
161
+ 'f': lambda: tmux('run-shell', '-b', f"sleep 0.2 && bash ~/.config/tmux/bin/float_term.sh '{PANE}'"),
162
+ 't': lambda: tmux('run-shell', '-b', f"sleep 0.2 && tmux popup -d '{cwd or H}' -xC -yC -w70% -h70% -E 'tmux new -A -s floating'"),
163
+ 'y': lambda: tmux('run-shell', '~/.tmux/plugins/tmux-resurrect/scripts/save.sh'),
164
+ 'r': lambda: tmux('run-shell', '~/.tmux/plugins/tmux-resurrect/scripts/restore.sh'),
165
+ 'c': lambda: tmux('copy-mode', *T),
166
+ 'i': lambda: tmux('run-shell', '-b', f"bash ~/.config/tmux/bin/paste_image_wrapper.sh '{PANE}'"),
167
+ 'o': lambda: tmux('run-shell', '-b', 'bash ~/.config/tmux/bin/open_clip.sh url'),
168
+ 'g': lambda: tmux('run-shell', '-b', 'bash ~/.config/tmux/bin/open_clip.sh search'),
169
+ }
170
+ if key in dispatch:
171
+ dispatch[key]()
172
+
173
+ def set_raw_input(fd):
174
+ """Enable raw input while keeping output processing (ONLCR) intact.
175
+ tty.setraw() disables OPOST which strips the CR from \\n, causing
176
+ each output line to drift right. We only want to affect input flags."""
177
+ new = termios.tcgetattr(fd)
178
+ new[0] &= ~(termios.BRKINT | termios.ICRNL | termios.INPCK |
179
+ termios.ISTRIP | termios.IXON)
180
+ new[2] = (new[2] & ~termios.CSIZE) | termios.CS8
181
+ new[3] &= ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG)
182
+ new[6][termios.VMIN] = 1
183
+ new[6][termios.VTIME] = 0
184
+ # new[1] (output flags) left untouched — keeps OPOST/ONLCR so \n → \r\n
185
+ termios.tcsetattr(fd, termios.TCSADRAIN, new)
186
+
187
+ def main():
188
+ fd = sys.stdin.fileno()
189
+ old = termios.tcgetattr(fd)
190
+
191
+ sc, sr = 0, 0
192
+ while not selectable(sc, sr) and sr < NR - 1: sr += 1
193
+
194
+ try:
195
+ set_raw_input(fd)
196
+ # Clear, hide cursor, enable SGR mouse + motion tracking
197
+ sys.stdout.write('\033[2J\033[H\033[?25l\033[?1000h\033[?1006h\033[?1003h')
198
+ sys.stdout.flush()
199
+ draw(sc, sr)
200
+
201
+ result = None
202
+ buf = b''
203
+
204
+ while result is None:
205
+ rdy, _, _ = select.select([fd], [], [], 2.0)
206
+ if not rdy: continue
207
+ buf += os.read(fd, 128)
208
+
209
+ while buf and result is None:
210
+ ch = buf[0:1]
211
+
212
+ if ch == b'\x1b':
213
+ if len(buf) < 2:
214
+ # Peek briefly to distinguish bare ESC from a multi-byte sequence
215
+ r2, _, _ = select.select([fd], [], [], 0.05)
216
+ if r2: buf += os.read(fd, 128)
217
+ if len(buf) < 2 or buf[1:2] != b'[':
218
+ result = '' # bare ESC → quit
219
+ break
220
+ i = 2
221
+ while i < len(buf) and buf[i] not in b'ABCDHFMPQRSmnlh~': i += 1
222
+ if i >= len(buf): break # incomplete sequence — wait for more
223
+ seq = buf[:i+1].decode('latin-1', errors='replace')
224
+ buf = buf[i+1:]
225
+
226
+ nc, nr = sc, sr
227
+ if seq == '\033[A': nc, nr = move(sc, sr, 0, -1) # up arrow
228
+ elif seq == '\033[B': nc, nr = move(sc, sr, 0, 1) # down arrow
229
+ elif seq == '\033[D': nc, nr = move(sc, sr, -1, 0) # left arrow
230
+ elif seq == '\033[C': nc, nr = move(sc, sr, 1, 0) # right arrow
231
+ else:
232
+ m = re.match(r'\x1b\[<(\d+);(\d+);(\d+)([Mm])', seq)
233
+ if m:
234
+ btn, mx, my, typ = int(m[1]), int(m[2]), int(m[3]), m[4]
235
+ if btn == 64: # scroll up → navigate up
236
+ nc, nr = move(sc, sr, 0, -1)
237
+ elif btn == 65: # scroll down → navigate down
238
+ nc, nr = move(sc, sr, 0, 1)
239
+ else:
240
+ c2, r2 = x2col(mx), y2row(my)
241
+ if c2 >= 0 and 0 <= r2 < NR and selectable(c2, r2):
242
+ if 32 <= btn < 64: # motion (not scroll) → hover
243
+ nc, nr = c2, r2
244
+ elif btn == 0 and typ == 'M': # left click → execute
245
+ nc, nr = c2, r2
246
+ if nc != sc or nr != sr:
247
+ old_sc, old_sr = sc, sr
248
+ sc, sr = nc, nr
249
+ update(old_sc, old_sr, sc, sr)
250
+ result = COLS[sc][sr][0]
251
+
252
+ if result is None and (nc != sc or nr != sr):
253
+ old_sc, old_sr = sc, sr
254
+ sc, sr = nc, nr
255
+ update(old_sc, old_sr, sc, sr)
256
+
257
+ elif ch in (b'\r', b'\n'):
258
+ buf = buf[1:]
259
+ if selectable(sc, sr): result = COLS[sc][sr][0]
260
+ else: result = ''
261
+
262
+ elif ch in (b'\x03', b'\x04'): # Ctrl+C / Ctrl+D
263
+ buf = buf[1:]
264
+ result = ''
265
+
266
+ else:
267
+ # Direct key: jump selection to matching action (no immediate execute)
268
+ key_char = ch.decode('latin-1', errors='ignore')
269
+ buf = buf[1:]
270
+ for cc in range(NC):
271
+ for rr in range(len(COLS[cc])):
272
+ if selectable(cc, rr) and COLS[cc][rr][0] == key_char:
273
+ if (cc, rr) != (sc, sr):
274
+ old_sc, old_sr = sc, sr
275
+ sc, sr = cc, rr
276
+ update(old_sc, old_sr, sc, sr)
277
+ break
278
+ else:
279
+ continue
280
+ break
281
+
282
+ finally:
283
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
284
+ sys.stdout.write('\033[?1003l\033[?1006l\033[?1000l\033[?25h\033[2J\033[H')
285
+ sys.stdout.flush()
286
+
287
+ if result:
288
+ execute(result)
289
+
290
+ if __name__ == '__main__':
291
+ main()
@@ -0,0 +1,110 @@
1
+ #!/bin/bash
2
+ # actions_popup.sh — fzf-powered actions menu
3
+ PANE="${1:-}"
4
+ FZF="${HOME}/.local/bin/fzf"
5
+
6
+ ITEMS=(
7
+ "z │ Zoom / unzoom pane"
8
+ "p │ Previous window"
9
+ "n │ Next window"
10
+ "w │ List windows & sessions"
11
+ "W │ New window"
12
+ "K │ Kill window"
13
+ "N │ Rename session"
14
+ "X │ Kill session"
15
+ "d │ Detach"
16
+ "───┼──────────────────────────────────"
17
+ "H │ Split top / bottom"
18
+ "V │ Split left / right"
19
+ "k │ Kill pane"
20
+ ", │ Rename window"
21
+ "s │ Session chooser"
22
+ "───┼──────────────────────────────────"
23
+ "u │ Swap pane up"
24
+ "v │ Swap pane down"
25
+ "< │ Swap pane left"
26
+ "> │ Swap pane right"
27
+ "e │ Break pane → new window"
28
+ "J │ Move pane to window…"
29
+ "L │ Pull pane from window…"
30
+ "───┼──────────────────────────────────"
31
+ "f │ Float terminal"
32
+ "t │ Scratch terminal (M-i)"
33
+ "M │ Share session (Muxile)"
34
+ "S │ Save session"
35
+ "R │ Restore session"
36
+ "───┼──────────────────────────────────"
37
+ "r │ Reload tmux config"
38
+ "c │ Copy mode (o=open S=search)"
39
+ "i │ Paste image"
40
+ "o │ Open clipboard URL/file"
41
+ "g │ Search clipboard in browser"
42
+ )
43
+
44
+ SELECTED=$(printf '%s\n' "${ITEMS[@]}" | "$FZF" \
45
+ --ansi \
46
+ --no-sort \
47
+ --layout=reverse \
48
+ --border=rounded \
49
+ --border-label=" ⚙ Actions " \
50
+ --border-label-pos=3 \
51
+ --color="bg:#303446,bg+:#414559,fg:#c6d0f5,fg+:#c6d0f5" \
52
+ --color="border:#626880,label:#ef9f76,prompt:#8caaee" \
53
+ --color="pointer:#e78284,hl:#8caaee,hl+:#8caaee" \
54
+ --prompt=" " \
55
+ --pointer="▶" \
56
+ --no-info \
57
+ --bind "esc:abort" \
58
+ --bind "left-click:accept" \
59
+ --with-nth=1..2 \
60
+ --delimiter="│")
61
+
62
+ [ -z "$SELECTED" ] && exit 0
63
+
64
+ IFS='│' read -r key _ <<< "$SELECTED"
65
+ key="${key//[[:space:]]/}"
66
+ [[ "$key" == "───" ]] && exit 0
67
+
68
+ T="${PANE:+-t $PANE}"
69
+ case "$key" in
70
+ # ── Window & session ────────────────────────────────────────────────────
71
+ z) tmux resize-pane -Z $T ;;
72
+ p) tmux previous-window ;;
73
+ n) tmux next-window ;;
74
+ w) tmux choose-tree -s ;;
75
+ W) CWD=$(tmux display-message -p ${PANE:+-t "$PANE"} '#{pane_current_path}' 2>/dev/null)
76
+ tmux new-window ${CWD:+-c "$CWD"} ;;
77
+ K) tmux confirm-before -p "Kill window? (y/n)" kill-window ;;
78
+ N) tmux command-prompt -I "#{session_name}" "rename-session -- '%%'" ;;
79
+ X) tmux confirm-before -p "Kill session? (y/n)" kill-session ;;
80
+ d) tmux confirm-before -p "Detach? (y/n)" detach-client ;;
81
+ # ── Pane controls ───────────────────────────────────────────────────────
82
+ H) CWD=$(tmux display-message -p ${PANE:+-t "$PANE"} '#{pane_current_path}' 2>/dev/null)
83
+ tmux split-window -v ${CWD:+-c "$CWD"} ;;
84
+ V) CWD=$(tmux display-message -p ${PANE:+-t "$PANE"} '#{pane_current_path}' 2>/dev/null)
85
+ tmux split-window -h ${CWD:+-c "$CWD"} ;;
86
+ k) tmux confirm-before -p "Kill pane? (y/n)" kill-pane $T ;;
87
+ ,) tmux command-prompt -I "#W" "rename-window -- '%%'" ;;
88
+ s) tmux choose-tree -s ;;
89
+ # ── Pane movement ───────────────────────────────────────────────────────
90
+ u) tmux swap-pane -U $T ;;
91
+ v) tmux swap-pane -D $T ;;
92
+ '<') tmux swap-pane -U $T ;;
93
+ '>') tmux swap-pane -D $T ;;
94
+ e) tmux break-pane $T ;;
95
+ J) tmux run-shell -b "sleep 0.2 && tmux choose-tree -w 'join-pane -t \"%%\"'" ;;
96
+ L) tmux run-shell -b "sleep 0.2 && tmux choose-tree -w 'join-pane -s \"%%\"'" ;;
97
+ # ── Tools ───────────────────────────────────────────────────────────────
98
+ f) tmux run-shell -b "sleep 0.2 && bash ~/.config/tmux/bin/float_term.sh '$PANE'" ;;
99
+ t) CWD=$(tmux display-message -p ${PANE:+-t "$PANE"} '#{pane_current_path}' 2>/dev/null)
100
+ tmux run-shell -b "sleep 0.2 && tmux popup -d '${CWD:-$HOME}' -xC -yC -w70% -h70% -E 'tmux new -A -s floating'" ;;
101
+ M) tmux run-shell -b "~/.tmux/plugins/muxile/scripts/main.sh" ;;
102
+ S) tmux run-shell "~/.tmux/plugins/tmux-resurrect/scripts/save.sh" ;;
103
+ R) tmux run-shell "~/.tmux/plugins/tmux-resurrect/scripts/restore.sh" ;;
104
+ # ── Clipboard & copy ────────────────────────────────────────────────────
105
+ r) tmux source-file ~/.tmux.conf \; display-message "Config reloaded" ;;
106
+ c) tmux copy-mode $T ;;
107
+ i) tmux run-shell -b "bash ~/.config/tmux/bin/paste_image_wrapper.sh '$PANE'" ;;
108
+ o) tmux run-shell -b "bash ~/.config/tmux/bin/open_clip.sh url" ;;
109
+ g) tmux run-shell -b "bash ~/.config/tmux/bin/open_clip.sh search" ;;
110
+ esac
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+ latest=$(ls -t ~/.claude/projects/*/*.jsonl 2>/dev/null | head -1)
3
+ if [ -n "$latest" ]; then
4
+ size=$(wc -c < "$latest")
5
+ pct=$(( size * 100 / 800000 ))
6
+ [ "$pct" -gt 100 ] && pct=100
7
+ filled=$(( pct / 10 ))
8
+ empty=$(( 10 - filled ))
9
+ bar=""
10
+ for ((i=0; i<filled; i++)); do bar+="█"; done
11
+ for ((i=0; i<empty; i++)); do bar+="░"; done
12
+ echo "${bar} ${pct}%"
13
+ else
14
+ echo "░░░░░░░░░░ --%"
15
+ fi
@@ -0,0 +1,26 @@
1
+ set -g @thm_rosewater "#f2d5cf"
2
+ set -g @thm_flamingo "#eebebe"
3
+ set -g @thm_pink "#f4b8e4"
4
+ set -g @thm_mauve "#ca9ee6"
5
+ set -g @thm_red "#e78284"
6
+ set -g @thm_maroon "#ea999c"
7
+ set -g @thm_peach "#ef9f76"
8
+ set -g @thm_yellow "#e5c890"
9
+ set -g @thm_green "#a6d189"
10
+ set -g @thm_teal "#81c8be"
11
+ set -g @thm_sky "#99d1db"
12
+ set -g @thm_sapphire "#85c1dc"
13
+ set -g @thm_blue "#8caaee"
14
+ set -g @thm_lavender "#babbf1"
15
+ set -g @thm_text "#c6d0f5"
16
+ set -g @thm_subtext1 "#b5bfe2"
17
+ set -g @thm_subtext0 "#a5adce"
18
+ set -g @thm_overlay2 "#949cbb"
19
+ set -g @thm_overlay1 "#838ba7"
20
+ set -g @thm_overlay0 "#737994"
21
+ set -g @thm_surface2 "#626880"
22
+ set -g @thm_surface1 "#51576d"
23
+ set -g @thm_surface0 "#414559"
24
+ set -g @thm_base "#303446"
25
+ set -g @thm_mantle "#292c3c"
26
+ set -g @thm_crust "#232634"
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ read -r _ u1 n1 s1 i1 _ < /proc/stat
3
+ sleep 0.3
4
+ read -r _ u2 n2 s2 i2 _ < /proc/stat
5
+ total=$(( (u2+n2+s2+i2) - (u1+n1+s1+i1) ))
6
+ idle=$(( i2 - i1 ))
7
+ [ "$total" -gt 0 ] && echo "$(( 100 * (total - idle) / total ))%" || echo "0%"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # dispatch.sh — handles status bar button clicks
3
+ # $1 = mouse_status_range (button tag), $2 = pane_id
4
+ PANE="${2:-}"
5
+
6
+ case "$1" in
7
+ reload) tmux source-file ~/.tmux.conf && tmux display-message "Config reloaded" ;;
8
+ actions) ;; # handled inline in tmux.conf MouseDown1Status binding
9
+ *) exit 0 ;;
10
+ esac
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # Float terminal shell init — sourced via bash --init-file
3
+ [ -f ~/.bashrc ] && source ~/.bashrc 2>/dev/null
4
+
5
+ _float_w() { cat "${FLOAT_STATE:-/tmp/tmux_float_size}" 2>/dev/null || echo 80; }
6
+
7
+ float_bigger() { printf $'\n'; exit 10; }
8
+ float_smaller() { printf $'\n'; exit 11; }
9
+
10
+ bind -x '"\e=": float_bigger' 2>/dev/null # Alt+=
11
+ bind -x '"\e-": float_smaller' 2>/dev/null # Alt+-
12
+
13
+ printf ' [%s%%] Alt+= bigger Alt+- smaller Ctrl+D close\n' "$(_float_w)"
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ # float_term.sh — resizable float terminal
3
+ # Exit codes from inner shell: 10 = bigger, 11 = smaller, else = close
4
+ PANE="${1:-}"
5
+ STATE="/tmp/tmux_float_size"
6
+ [ -f "$STATE" ] || echo "80" > "$STATE"
7
+
8
+ while true; do
9
+ W=$(cat "$STATE" 2>/dev/null); W=${W:-80}
10
+ CWD=$(tmux display-message -p ${PANE:+-t "$PANE"} '#{pane_current_path}' 2>/dev/null)
11
+
12
+ tmux display-popup -E \
13
+ -d "${CWD:-$HOME}" -xC -yC \
14
+ -w "${W}%" -h "${W}%" \
15
+ "FLOAT_STATE='$STATE' bash --init-file ~/.config/tmux/bin/float_init.sh -i"
16
+
17
+ rc=$?
18
+ case $rc in
19
+ 10) NEW=$(( W + 10 )); [ $NEW -gt 96 ] && NEW=96; echo "$NEW" > "$STATE" ;;
20
+ 11) NEW=$(( W - 10 )); [ $NEW -lt 30 ] && NEW=30; echo "$NEW" > "$STATE" ;;
21
+ *) break ;;
22
+ esac
23
+ done
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ # open_clip.sh — open clipboard as URL/file or browser search
3
+ # Usage: open_clip.sh [url|search]
4
+ MODE="${1:-url}"
5
+ CLIP=$(powershell.exe -NoProfile -Command \
6
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-Clipboard" \
7
+ 2>/dev/null | tr -d '\r' | head -1)
8
+ [ -z "$CLIP" ] && exit 1
9
+
10
+ case "$MODE" in
11
+ url) powershell.exe Start "$CLIP" 2>/dev/null ;;
12
+ search) ENC=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$CLIP" 2>/dev/null || printf '%s' "$CLIP")
13
+ powershell.exe Start "https://www.google.com/search?q=${ENC}" 2>/dev/null ;;
14
+ esac
@@ -0,0 +1,26 @@
1
+ #!/bin/bash
2
+ # paste_claude.sh — paste Windows clipboard into any tmux pane with bracketed paste support
3
+ #
4
+ # Claude Code (and most modern TUI apps) use bracketed paste mode (\e[200~ ... \e[201~).
5
+ # Raw tmux paste-buffer -p bypasses these wrappers — Claude drops the input entirely.
6
+ # This script wraps the clipboard content in the correct bracketed paste escape sequences
7
+ # using tmux send-keys, which passes literal bytes directly to the pane's stdin.
8
+ #
9
+ # Usage (called from tmux binding):
10
+ # bash ~/.config/tmux/bin/paste_claude.sh '#{pane_id}'
11
+ #
12
+ # Bound to C-v in tmux.conf (replaces the old paste-buffer -p binding).
13
+
14
+ PANE="${1:-}"
15
+
16
+ # Get clipboard content from Windows
17
+ CONTENT=$(powershell.exe -NoProfile -Command \
18
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-Clipboard" \
19
+ 2>/dev/null | tr -d '\r')
20
+
21
+ [ -z "$CONTENT" ] && exit 0
22
+
23
+ # Send with bracketed paste sequences so Claude Code (and readline/readline-like TUIs) accept it.
24
+ # \e[200~ = paste start, \e[201~ = paste end
25
+ # send-keys without -l interprets the escape sequences correctly as terminal control codes.
26
+ tmux send-keys -t "$PANE" $'\e[200~'"${CONTENT}"$'\e[201~'
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # paste_clipboard.sh — paste text from Windows clipboard into tmux
3
+ TMPFILE=$(mktemp /tmp/tmux_paste_XXXXXX.txt)
4
+ powershell.exe -NoProfile -Command \
5
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-Clipboard" \
6
+ 2>/dev/null | tr -d '\r' > "$TMPFILE"
7
+ BYTE_COUNT=$(wc -c < "$TMPFILE")
8
+ PREVIEW=$(head -c 80 "$TMPFILE" | cat -v)
9
+ tmux display-message "Clipboard bytes:${BYTE_COUNT} | ${PREVIEW}"
10
+ if [ -s "$TMPFILE" ]; then
11
+ tmux load-buffer "$TMPFILE" && tmux paste-buffer -p
12
+ fi
13
+ rm -f "$TMPFILE"