@damian87/omp 0.6.0 → 0.7.0

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.
@@ -1,102 +1,127 @@
1
1
  ---
2
2
  name: team
3
- description: Split an approved plan into parallel tmux panes, each running an independent Copilot CLI agent. Use when work has independent lanes and you want visual parallel execution in split terminals. Use when user says /team, team, or wants parallel agent execution.
4
- argument-hint: "<number of lanes or plan reference>"
3
+ description: Split an approved plan into parallel tmux panes in the current window so the user can watch agents work. Prefer this visual flow by default; use `omp team` only when the user explicitly wants background execution or runtime messaging/status APIs.
4
+ argument-hint: "<number of workers> <task description>"
5
5
  ---
6
6
 
7
7
  # Team — tmux-based parallel agent execution
8
8
 
9
- `/team` splits work into parallel tmux panes in the **current window**, each running an independent interactive agent session. You see all agents working side-by-side immediately.
9
+ `/team` launches independent Copilot CLI agents in parallel tmux panes.
10
+
11
+ **Default behavior:** use the **split-window** flow so the user sees agents working in the current tmux window.
12
+
13
+ Use **runtime mode** (`omp team`) only when the user explicitly asks for background execution, detached monitoring, or runtime APIs like status, nudging, or worker messaging.
14
+
15
+ Two modes available:
16
+
17
+ | Mode | Command | Panes visible in | Best for |
18
+ |------|---------|-------------------|----------|
19
+ | **Split** | `team-launch.sh` | Current window | **Default**. Visual demo, watching agents work |
20
+ | **Runtime** | `omp team N:copilot "task"` | Separate tmux session | Explicit background jobs, task tracking, nudging, messaging |
10
21
 
11
22
  ## When to use
12
23
 
13
24
  - Work has **independent lanes** (no shared files, no ordering constraints)
14
- - You want visual, demo-friendly parallel execution in split terminals
25
+ - You want parallel execution in split terminals
15
26
 
16
- ## Agent execution steps (FOLLOW EXACTLY)
27
+ ## Default mode Split window (`team-launch.sh`)
17
28
 
18
- When `/team` is invoked, you MUST execute these steps in order:
29
+ Use this unless the user asks for background execution.
19
30
 
20
- ### Step 1 Identify lanes
31
+ ### When to choose it
21
32
 
22
- Collect independent work lanes from the conversation context. Each lane needs:
23
- - `id`: short kebab-case identifier (e.g. `lane-a`, `fix-auth`)
24
- - `name`: human-readable name (e.g. `Upgrade dependencies`)
25
- - `prompt`: complete task prompt — must be self-contained with all context the agent needs (files to change, what to do, commit message)
33
+ - The user wants to **see** the agents working
34
+ - You want panes in the **current tmux window**
35
+ - You are demoing or smoke-testing the skill
26
36
 
27
- If no plan or lanes exist yet, ask the user what work to split.
37
+ ### Step 1 Write lanes JSON
28
38
 
29
- ### Step 2 Write lanes JSON
30
-
31
- Write a temporary lanes file at `/tmp/team-lanes-<timestamp>.json`:
39
+ Write a temporary file at `/tmp/team-lanes-<timestamp>.json`:
32
40
 
33
41
  ```json
34
42
  [
35
- {
36
- "id": "lane-a",
37
- "name": "Short descriptive name",
38
- "prompt": "Complete self-contained task prompt for the agent..."
39
- },
40
- {
41
- "id": "lane-b",
42
- "name": "Another lane name",
43
- "prompt": "Another complete task prompt..."
44
- }
43
+ { "id": "lane-a", "name": "Short name", "prompt": "Complete self-contained task prompt..." },
44
+ { "id": "lane-b", "name": "Another lane", "prompt": "Another task prompt..." }
45
45
  ]
46
46
  ```
47
47
 
48
- ### Step 3 — Launch the team
49
-
50
- Run the launch script, passing the session name and lanes file path:
48
+ ### Step 2 — Launch
51
49
 
50
+ ```bash
51
+ bash ~/.copilot/installed-plugins/oh-my-copilot/oh-my-copilot/.github/skills/team/scripts/team-launch.sh \
52
+ --session "team-<name>" --lanes <lanes-file>
52
53
  ```
53
- .github/skills/team/scripts/team-launch.sh --session "team-<name>" --lanes <lanes-file>
54
+
55
+ The script:
56
+ 1. Splits the **current window** into panes
57
+ 2. Launches `omp --madmax` in each
58
+ 3. Auto-accepts folder trust prompts
59
+ 4. Waits for readiness, sends prompts
60
+ 5. Monitors completion and prints a results summary
61
+
62
+ ### Step 3 — Report
63
+
64
+ The script blocks and prints all results. Relay the output to the user.
65
+
66
+ ## Optional mode — Runtime (`omp team`)
67
+
68
+ Choose this only when the user explicitly wants the team to run in the background or needs runtime features.
69
+
70
+ ### When to choose it
71
+
72
+ - The user asked for a **background** team
73
+ - You need `omp team status`, shutdown, or runtime task APIs
74
+ - You do **not** need the panes in the current tmux window
75
+
76
+ ### Launch
77
+
78
+ ```bash
79
+ omp team <N>:copilot "<task description>"
54
80
  ```
55
81
 
56
- This will:
57
- - Split the **current tmux window** into panes (leader keeps its pane)
58
- - Launch `omp --madmax` interactively in each pane, then send the prompt
59
- - Arrange panes in a tiled grid layout
60
- - Print pane IDs and navigation commands
82
+ The runtime automatically:
83
+ 1. Creates a tmux session with split panes
84
+ 2. Launches `copilot --allow-all-tools` in each pane
85
+ 3. Auto-accepts folder trust prompts
86
+ 4. Waits for readiness, then sends the task prompt
87
+ 5. Tracks task state, heartbeats, and supports idle-nudging
61
88
 
62
- ### Step 4 — Report to user
89
+ ### Monitor and cleanup
63
90
 
64
- Show the user:
65
- - Which panes were created and what each is working on
66
- - Navigation: `Ctrl-b + arrow keys` to move between panes
67
- - How to check output: `tmux capture-pane -t <pane-id> -p -S -50`
68
- - How to kill panes when done
91
+ ```bash
92
+ omp team status <team-name> # check progress
93
+ tmux attach -t omp-team-<name> # watch panes live
94
+ omp team shutdown <team-name> # kill when done
95
+ ```
69
96
 
70
97
  ## Prerequisites
71
98
 
72
- - `tmux` installed and running inside a tmux session
73
- - `omp` (oh-my-copilot) on PATH — preferred, launches with `omp --madmax`
74
- - Falls back to `copilot` if `omp` is not available
75
- - `jq` for JSON parsing
99
+ - `tmux` installed and session running
100
+ - `omp` on PATH
101
+ - `jq` for JSON parsing (split mode only)
76
102
 
77
- ## Prompt guidelines
103
+ ## Task / prompt guidelines
78
104
 
79
- Each lane prompt must be **self-contained**. The agent in that pane has no context from this session. Include:
105
+ Each task must be **self-contained**. The agent has no context from this session. Include:
80
106
  - Exact files or directories to work in
81
- - What to do (fix, upgrade, accept, etc.)
82
- - How to verify (run tests, npm audit, etc.)
107
+ - What to do (fix, upgrade, etc.)
108
+ - How to verify (run tests, etc.)
83
109
  - Commit message to use
84
110
 
85
- ### Good prompt example
111
+ ### Good example
86
112
 
87
- > You are working in /Users/me/project. In src/auth/login.ts, replace the bcrypt password check with argon2. Update the import, change the verify call, and run `npm test -- --grep auth` to confirm. Commit with message "refactor: switch password hashing to argon2".
113
+ > In src/auth/login.ts, replace bcrypt with argon2. Update the import, change the verify call, run `npm test -- --grep auth`. Commit: "refactor: switch to argon2".
88
114
 
89
- ### Bad prompt example
115
+ ### Bad example
90
116
 
91
- > Fix the auth module. (Too vague — which file? What fix? How to verify?)
117
+ > Fix the auth module. (Too vague)
92
118
 
93
119
  ## Composition
94
120
 
95
- Use `/ralplan` before `/team` to produce the plan that defines lanes. Use `/verify` after all panes complete to confirm combined results don't conflict.
121
+ Use `/ralplan` before `/team` to produce the plan. Use `/verify` after completion.
96
122
 
97
123
  ## Limitations
98
124
 
99
- - Each pane is an independent agent session — no shared state or messaging
100
- - Agents cannot communicate with each other if tasks depend on each other, use `/ralph` instead
101
- - Leader (you) must manually verify results after all panes complete
102
- - Best for independent, non-conflicting work streams
125
+ - Each pane is an independent session — no shared state
126
+ - Workers can message each other via `omp team api send-message` (runtime mode only)
127
+ - If tasks depend on each other, use `/ralph` instead
@@ -7,9 +7,11 @@ set -euo pipefail
7
7
  # Usage:
8
8
  # team-launch.sh --session <name> --lanes <lanes.json>
9
9
  #
10
- # Each pane launches an interactive `omp --madmax` (or `copilot`) session,
11
- # then sends the lane prompt via tmux send-keys so the agent stays alive
12
- # for follow-up interaction.
10
+ # Flow:
11
+ # 1. Split panes and launch agent CLI in each
12
+ # 2. Wait for each agent to be ready (auto-accept folder trust prompts)
13
+ # 3. Send lane prompts via send-keys
14
+ # 4. Monitor until all agents finish, then print summary
13
15
 
14
16
  SESSION=""
15
17
  LANES_FILE=""
@@ -33,13 +35,11 @@ if [[ ! -f "$LANES_FILE" ]]; then
33
35
  fi
34
36
 
35
37
  if ! command -v tmux &>/dev/null; then
36
- echo "tmux not found" >&2
37
- exit 1
38
+ echo "tmux not found" >&2; exit 1
38
39
  fi
39
40
 
40
41
  if [[ -z "${TMUX:-}" ]]; then
41
- echo "Not inside a tmux session. Run this from within tmux." >&2
42
- exit 1
42
+ echo "Not inside a tmux session. Run this from within tmux." >&2; exit 1
43
43
  fi
44
44
 
45
45
  if command -v omp &>/dev/null; then
@@ -47,31 +47,60 @@ if command -v omp &>/dev/null; then
47
47
  elif command -v copilot &>/dev/null; then
48
48
  AGENT_CMD="copilot"
49
49
  else
50
- echo "Neither omp nor copilot CLI found" >&2
51
- exit 1
50
+ echo "Neither omp nor copilot CLI found" >&2; exit 1
52
51
  fi
53
52
 
54
53
  LANE_COUNT=$(jq length "$LANES_FILE")
55
54
  if [[ "$LANE_COUNT" -lt 1 ]]; then
56
- echo "No lanes defined in $LANES_FILE" >&2
57
- exit 1
55
+ echo "No lanes defined in $LANES_FILE" >&2; exit 1
58
56
  fi
59
57
 
60
58
  CWD=$(pwd)
61
- WAIT_SECS="${TEAM_AGENT_WAIT:-5}"
59
+ POLL="${TEAM_POLL_INTERVAL:-2}"
60
+ MAX_READY="${TEAM_MAX_READY_WAIT:-60}"
61
+ MAX_DONE="${TEAM_MAX_COMPLETION_WAIT:-300}"
62
+
63
+ # ── helpers ──────────────────────────────────────────────────────────
64
+
65
+ # Capture visible pane content (last N lines)
66
+ pane_text() { tmux capture-pane -t "$1" -p -S "-${2:-20}" 2>/dev/null || true; }
67
+
68
+ # Wait until the agent CLI is fully ready ('/ commands' status bar visible).
69
+ # Auto-accepts the folder-trust dialog if it appears.
70
+ wait_for_ready() {
71
+ local pane="$1" elapsed=0 accepted=0
72
+ while (( elapsed < MAX_READY )); do
73
+ local txt
74
+ txt=$(pane_text "$pane" 25)
75
+
76
+ # Ready: the '/ commands' status bar means the CLI input prompt is active
77
+ if echo "$txt" | grep -q '/ commands'; then
78
+ return 0
79
+ fi
80
+
81
+ # Auto-accept folder trust dialog
82
+ if (( accepted == 0 )) && echo "$txt" | grep -q 'Do you trust'; then
83
+ tmux send-keys -t "$pane" C-m
84
+ accepted=1
85
+ echo " ↳ Auto-accepted folder trust for $pane"
86
+ fi
87
+
88
+ sleep "$POLL"
89
+ elapsed=$((elapsed + POLL))
90
+ done
91
+ return 1
92
+ }
93
+
94
+ # ── step 1: create panes ─────────────────────────────────────────────
62
95
 
63
96
  echo "🚀 Splitting current window into $LANE_COUNT panes ($SESSION)"
64
97
  echo ""
65
98
 
66
- # Collect pane IDs as we create them
67
99
  PANE_IDS=()
68
-
69
100
  for i in $(seq 0 $((LANE_COUNT - 1))); do
70
101
  LANE_NAME=$(jq -r ".[$i].name" "$LANES_FILE")
71
- LANE_PROMPT=$(jq -r ".[$i].prompt" "$LANES_FILE")
72
102
  LANE_ID=$(jq -r ".[$i].id" "$LANES_FILE")
73
103
 
74
- # Split: alternate horizontal/vertical for a grid
75
104
  if (( i == 0 )); then
76
105
  PANE_ID=$(tmux split-window -h -c "$CWD" -P -F '#{pane_id}')
77
106
  elif (( i % 2 == 1 )); then
@@ -79,54 +108,123 @@ for i in $(seq 0 $((LANE_COUNT - 1))); do
79
108
  else
80
109
  PANE_ID=$(tmux split-window -v -t "${PANE_IDS[$((i-2))]}" -c "$CWD" -P -F '#{pane_id}')
81
110
  fi
82
-
83
111
  PANE_IDS+=("$PANE_ID")
84
112
 
85
- # Set pane title
86
113
  tmux select-pane -t "$PANE_ID" -T "$LANE_ID: $LANE_NAME"
114
+ tmux send-keys -t "$PANE_ID" "$AGENT_CMD" C-m
87
115
 
88
- # Launch interactive agent session (NOT with -p)
89
- tmux send-keys -t "$PANE_ID" "echo '═══ $LANE_NAME ═══' && $AGENT_CMD" C-m
90
-
91
- echo " ✅ Pane $PANE_ID → $LANE_NAME (launching agent...)"
116
+ echo " ✅ Pane $PANE_ID $LANE_NAME"
92
117
  done
93
118
 
94
- # Rebalance layout
95
119
  tmux select-layout tiled
96
120
 
97
- # Wait for agents to start up before sending prompts
121
+ # ── step 2: wait for readiness ───────────────────────────────────────
122
+
98
123
  echo ""
99
- echo "⏳ Waiting ${WAIT_SECS}s for agents to initialise..."
100
- sleep "$WAIT_SECS"
124
+ echo "⏳ Waiting for agents to initialise (up to ${MAX_READY}s)..."
101
125
 
102
- # Now send prompts to each interactive session
126
+ for i in $(seq 0 $((LANE_COUNT - 1))); do
127
+ PANE_ID="${PANE_IDS[$i]}"
128
+ LANE_NAME=$(jq -r ".[$i].name" "$LANES_FILE")
129
+ if wait_for_ready "$PANE_ID"; then
130
+ echo " ✅ $PANE_ID ($LANE_NAME) ready"
131
+ else
132
+ echo " ⚠️ $PANE_ID ($LANE_NAME) not ready after ${MAX_READY}s — sending anyway"
133
+ fi
134
+ done
135
+
136
+ # ── step 3: send prompts (literal text + Enter separately) ───────────
137
+
138
+ echo ""
139
+ echo "📨 Sending prompts..."
103
140
  for i in $(seq 0 $((LANE_COUNT - 1))); do
104
141
  LANE_PROMPT=$(jq -r ".[$i].prompt" "$LANES_FILE")
105
142
  LANE_NAME=$(jq -r ".[$i].name" "$LANES_FILE")
106
143
  PANE_ID="${PANE_IDS[$i]}"
107
144
 
108
- # Write prompt to temp file and use tmux load-buffer + paste for reliable delivery
109
- PROMPT_FILE="/tmp/team-prompt-${SESSION}-${i}.txt"
110
- printf '%s' "$LANE_PROMPT" > "$PROMPT_FILE"
111
-
112
- # Send via tmux send-keys -l (literal) then Enter
113
- tmux send-keys -t "$PANE_ID" -l "$(cat "$PROMPT_FILE")"
145
+ # -l = literal (no key interpretation), then C-m = Enter as a separate call
146
+ tmux send-keys -t "$PANE_ID" -l "$LANE_PROMPT"
147
+ sleep 0.3
114
148
  tmux send-keys -t "$PANE_ID" C-m
115
149
 
116
- echo " 📨 Sent prompt to $PANE_ID ($LANE_NAME)"
150
+ echo " 📨 Sent to $PANE_ID ($LANE_NAME)"
117
151
  done
118
152
 
119
- # Switch focus back to the original (leader) pane
120
153
  tmux select-pane -t '{left}'
121
154
 
155
+ # ── step 4: monitor completion ───────────────────────────────────────
156
+
122
157
  echo ""
123
- echo " $LANE_COUNT interactive agent sessions running in split panes"
158
+ echo " Monitoring agents for completion (up to ${MAX_DONE}s)..."
159
+
160
+ # Brief pause so agents start processing before we poll
161
+ sleep 5
162
+
163
+ # State per lane: 0=waiting-for-busy, 1=busy, 2=done
164
+ LANE_STATE=()
165
+ for i in $(seq 0 $((LANE_COUNT - 1))); do LANE_STATE[$i]=0; done
166
+
167
+ COMPLETED=0
168
+ ELAPSED=0
169
+ while (( COMPLETED < LANE_COUNT && ELAPSED < MAX_DONE )); do
170
+ sleep "$POLL"
171
+ ELAPSED=$((ELAPSED + POLL))
172
+
173
+ for i in $(seq 0 $((LANE_COUNT - 1))); do
174
+ [[ "${LANE_STATE[$i]}" == "2" ]] && continue
175
+
176
+ PANE_ID="${PANE_IDS[$i]}"
177
+ LANE_NAME=$(jq -r ".[$i].name" "$LANES_FILE")
178
+
179
+ # Pane died?
180
+ if ! tmux list-panes -a -F '#{pane_id}' 2>/dev/null | grep -q "^${PANE_ID}$"; then
181
+ echo " ❌ $PANE_ID ($LANE_NAME) — pane died"
182
+ LANE_STATE[$i]=2; COMPLETED=$((COMPLETED + 1)); continue
183
+ fi
184
+
185
+ local_capture=$(pane_text "$PANE_ID" 15)
186
+
187
+ # State 0→1: agent started working (response marker ● appears)
188
+ if [[ "${LANE_STATE[$i]}" == "0" ]]; then
189
+ if echo "$local_capture" | grep -q '●'; then
190
+ LANE_STATE[$i]=1
191
+ fi
192
+ continue
193
+ fi
194
+
195
+ # State 1→2: agent finished (back to idle — '/ commands' in last 5 lines)
196
+ if echo "$local_capture" | tail -5 | grep -q '/ commands'; then
197
+ echo " ✅ $PANE_ID ($LANE_NAME) — agent finished"
198
+ LANE_STATE[$i]=2; COMPLETED=$((COMPLETED + 1))
199
+ fi
200
+ done
201
+ done
202
+
124
203
  echo ""
125
- echo "Pane IDs: ${PANE_IDS[*]}"
204
+ if (( COMPLETED == LANE_COUNT )); then
205
+ echo "🎉 All $LANE_COUNT agents completed!"
206
+ else
207
+ echo "⏰ Timeout — $((LANE_COUNT - COMPLETED)) agent(s) still running"
208
+ fi
209
+
210
+ # ── summary ──────────────────────────────────────────────────────────
211
+
126
212
  echo ""
127
- echo "Commands:"
128
- echo " tmux select-layout tiled # rebalance layout"
129
- echo " tmux capture-pane -t <pane-id> -p -S -50 # read pane output"
130
- echo " Ctrl-b + arrow keys # navigate between panes"
213
+ echo "═══ Results Summary ═══"
214
+ for i in $(seq 0 $((LANE_COUNT - 1))); do
215
+ PANE_ID="${PANE_IDS[$i]}"
216
+ LANE_NAME=$(jq -r ".[$i].name" "$LANES_FILE")
217
+ LANE_ID=$(jq -r ".[$i].id" "$LANES_FILE")
218
+ echo ""
219
+ echo "── $LANE_ID: $LANE_NAME ($PANE_ID) ──"
220
+ if tmux list-panes -a -F '#{pane_id}' 2>/dev/null | grep -q "^${PANE_ID}$"; then
221
+ pane_text "$PANE_ID" 40 | grep -E '●|✅|❌|⚠|error|Error|FAIL|PASS|done|Done' | tail -10 || echo " (no notable output captured)"
222
+ else
223
+ echo " (pane no longer exists)"
224
+ fi
225
+ done
226
+
131
227
  echo ""
132
- echo "💡 Agents are interactive — you can send follow-up prompts to any pane"
228
+ echo "Pane IDs: ${PANE_IDS[*]}"
229
+ echo "Navigate: Ctrl-b + arrow keys"
230
+ echo "💡 Agents are interactive — send follow-up prompts to any pane"
package/README.md CHANGED
@@ -82,6 +82,7 @@ That's it.
82
82
  ### Developer Experience
83
83
 
84
84
  - **MCP server** ships with `notepad`, `project-memory`, `shared-memory`, `state`, and `trace` tools out of the box
85
+ - **Lightweight Copilot context** — managed instructions keep only the repo goal plus on-demand memory commands; set `OMP_DISABLE_INSTRUCTIONS_MEMORY=1` to skip writing the managed block entirely
85
86
  - **File-state coordination** — outbox JSONL + byte cursor, atomic `O_EXCL` task locks, optimistic CAS on claim
86
87
  - **Idle nudge** — content-based pane idle detection that pokes stuck workers
87
88
  - **Mode-state loops** — single source of truth per loop (Ralph/Ultrawork/UltraQA state files)
@@ -183,6 +184,7 @@ omp list # show discovered skills and agents
183
184
  omp setup [--dry-run] [--scope project|user]
184
185
  omp launch -- [copilot flags…] # forward arbitrary args to copilot
185
186
  omp --madmax -p "edit src/foo.ts" # bare-flag, maps to copilot --yolo
187
+ omp suggest "fix flaky tests" # recommend a slash-skill workflow
186
188
  omp team 3:executor "fix all type errors" # spawn tmux workers
187
189
  omp team status <name>
188
190
  omp team shutdown <name>
@@ -91,8 +91,8 @@
91
91
  "source": ".github/skills/team/SKILL.md",
92
92
  "sourcePath": ".github/skills/team/SKILL.md",
93
93
  "canonicalPath": ".github/skills/team/SKILL.md",
94
- "description": "Split approved plans into parallel lanes.",
95
- "summary": "Split approved plans into parallel lanes.",
94
+ "description": "Split approved plans into visible tmux lanes in the current window by default.",
95
+ "summary": "Split approved plans into visible tmux lanes by default.",
96
96
  "support": "project-skill",
97
97
  "aliases": [],
98
98
  "slashCommands": [
package/dist/src/cli.d.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env node
2
- interface CliResult {
3
- ok: boolean;
4
- exitCode?: number;
5
- output?: unknown;
6
- message?: string;
7
- }
2
+ import type { CliResult } from "./commands/types.js";
8
3
  export declare function runCli(argv?: string[]): Promise<CliResult>;
9
4
  /** Parse a --models value: comma-separated `model` or `model:role:weight` tokens. */
10
5
  export declare function parseModelsFlag(value: string): {
@@ -14,4 +9,3 @@ export declare function parseModelsFlag(value: string): {
14
9
  }[];
15
10
  /** Parse a numeric flag as a finite positive integer; throw on malformed input. */
16
11
  export declare function parsePositiveIntFlag(value: string | undefined, flag: string): number | undefined;
17
- export {};
package/dist/src/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { realpathSync } from "node:fs";
3
3
  import { pathToFileURL } from "node:url";
4
4
  import { findCapability, loadCatalogBundle, validateCatalogBundle } from "./catalog.js";
5
+ import { findRegisteredCommand, registeredCommandHelpLines } from "./commands/registry.js";
5
6
  import { inspectProject } from "./project.js";
6
7
  function hasFlag(args, flag) {
7
8
  return args.includes(flag);
@@ -22,7 +23,7 @@ function printResult(result, json) {
22
23
  }
23
24
  }
24
25
  function help() {
25
- return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron \"<expr>\" --prompt \"<text>\" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\n schedule run-now <id> [--json]\n schedule remove <id> [--json]\n goal set "<objective>" [--json]\n goal read [--json]\n memory sync [--json] (render goal+directives into copilot-instructions.md)\n daily-log set-goal "<text>" [--json]\n daily-log add "<text>" [--json]\n daily-log read [--days <n>] [--json]\n daily-log prune [--keep-days <n>] [--json]\n state write <key> <val> [--ttl <s>] | read|delete|status <key> | list | cleanup [--json]\n project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" [--json]\n trace timeline [<sessionId>] [--limit <n>] | summary [<sessionId>] | add <sessionId> <event> [<json>] [--json]\n catalog list [--json]\n catalog validate [--json]\n catalog capability <id> [--json]\n project inspect [--json]\n skill install <skill-dir> [--root <repo>] [--scope project|user] [--dry-run] [--json]\n lint:skills [--root <repo>]\n sync:dry-run [--root <repo>]\n jira:dry-run [--root <repo>]\n jira render <plan-file> [--root <repo>] [--json]\n jira apply <ticket-key-or-plan-file> --comment|--update|--transition|--link [--dry-run] [--json]\n`;
26
+ return `oh-my-copilot\n\nRun \`omp\` with no arguments to launch copilot (permissions bypass OFF).\nUse \`omp help\` to show this list.\n\nCommands:\n (no args) launch copilot (bypass OFF by default)\n version [--json]\n list [--json]\n setup [--dry-run] [--scope project|user] [--plugin-root <dir>] [--json]\n doctor [--json] [--copilot-bin <path>] [--skip-copilot]\n launch -- <args...>\n --madmax [args...] (bare-flag launch with permissions bypass; alias of --yolo)\n team <N:role> "<task>" [--name <name>] [--json]\n team status <name> [--json]\n team shutdown <name> [--json]\n team api claim-task --input '<json>' [--json]\n team api transition-task-status --input '<json>' [--json]\n team api send-message --input '<json>' [--json]\n team api broadcast --input '<json>' [--json]\n team api mailbox-list --input '<json>' [--json]\n team api mailbox-mark-delivered --input '<json>' [--json]\n council "<question>" [--models a,b,c|m:role:weight] [--context <text|@file>] [--rubric <text|@file>] [--synth <model>] [--probe] [--timeout <ms>] [--synth-timeout <ms>] [--min-survivors <n>] [--max-concurrency <n>] [--tmp-dir <dir>] [--json]\n${registeredCommandHelpLines().join("\n")}\n ralph start "<task>" [--max-iterations <n>] [--session-id <id>] [--json]\n ralph status [--json]\n ralph tick [--json]\n ralph cancel [--json]\n ultrawork start "<objective>" [--task-count <n>] [--summary <s>] [--json]\n ultrawork status [--json]\n ultrawork cancel [--json]\n ultraqa start "<goal>" [--max-cycles <n>] [--json]\n ultraqa cycle pass|fail|pending [--json]\n ultraqa status [--json]\n ultraqa cancel [--json]\n schedule add --id <id> --cron "<expr>" --prompt "<text>" [--bin copilot] [--model <m>] [--cwd <dir>] [--timeout <ms>] [--max-runs <n>] [--ttl-hours <h>] [--allow-all-tools] [--dry-run] [--json]\n schedule list [--json]\n schedule status <id> [--json]\n schedule run-now <id> [--json]\n schedule remove <id> [--json]\n goal set "<objective>" [--json]\n goal read [--json]\n memory sync [--json] (render goal+directives into copilot-instructions.md)\n daily-log set-goal "<text>" [--json]\n daily-log add "<text>" [--json]\n daily-log read [--days <n>] [--json]\n daily-log prune [--keep-days <n>] [--json]\n state write <key> <val> [--ttl <s>] | read|delete|status <key> | list | cleanup [--json]\n project-memory read [<id>] | index | add-note "<title>" [--body "<text>"] | add-directive "<rule>" [--json]\n trace timeline [<sessionId>] [--limit <n>] | summary [<sessionId>] | add <sessionId> <event> [<json>] [--json]\n catalog list [--json]\n catalog validate [--json]\n catalog capability <id> [--json]\n project inspect [--json]\n skill install <skill-dir> [--root <repo>] [--scope project|user] [--dry-run] [--json]\n lint:skills [--root <repo>]\n sync:dry-run [--root <repo>]\n jira:dry-run [--root <repo>]\n jira render <plan-file> [--root <repo>] [--json]\n jira apply <ticket-key-or-plan-file> --comment|--update|--transition|--link [--dry-run] [--json]\n`;
26
27
  }
27
28
  async function resolveExistingInputPath(value) {
28
29
  const { existsSync } = await import("node:fs");
@@ -114,6 +115,10 @@ export async function runCli(argv = process.argv.slice(2)) {
114
115
  if (group === "team") {
115
116
  return await handleTeamCommand(argv, json);
116
117
  }
118
+ const registeredCommand = findRegisteredCommand(group);
119
+ if (registeredCommand) {
120
+ return await registeredCommand.run(argv, { cwd: flagValue(argv, "--root") ?? process.cwd(), json });
121
+ }
117
122
  if (group === "council") {
118
123
  return await handleCouncilCommand(argv, json);
119
124
  }
@@ -616,6 +621,46 @@ async function handleTeamCommand(argv, json) {
616
621
  message: `shutdown team ${value} killedPanes=${result.killedPanes} killedSession=${result.killedSession}`,
617
622
  };
618
623
  }
624
+ if (command === "monitor-panes") {
625
+ const leaderPaneId = flagValue(argv, "--leader-pane");
626
+ const workerPaneIds = argv
627
+ .flatMap((arg, index) => (arg === "--worker-pane" ? [argv[index + 1]] : []))
628
+ .filter((value) => Boolean(value));
629
+ if (!leaderPaneId || workerPaneIds.length === 0) {
630
+ return { ok: false, exitCode: 1, message: "team monitor-panes requires --leader-pane and at least one --worker-pane" };
631
+ }
632
+ let pollIntervalMs;
633
+ let readySamples;
634
+ let minObservationMs;
635
+ let timeoutMs;
636
+ let captureLines;
637
+ try {
638
+ pollIntervalMs = parsePositiveIntFlag(flagValue(argv, "--poll-interval-ms"), "--poll-interval-ms");
639
+ readySamples = parsePositiveIntFlag(flagValue(argv, "--ready-samples"), "--ready-samples");
640
+ minObservationMs = parsePositiveIntFlag(flagValue(argv, "--min-observation-ms"), "--min-observation-ms");
641
+ timeoutMs = parsePositiveIntFlag(flagValue(argv, "--timeout-ms"), "--timeout-ms");
642
+ captureLines = parsePositiveIntFlag(flagValue(argv, "--capture-lines"), "--capture-lines");
643
+ }
644
+ catch (err) {
645
+ return { ok: false, exitCode: 1, message: String(err instanceof Error ? err.message : err) };
646
+ }
647
+ const { monitorPanes } = await import("./team/pane-monitor.js");
648
+ const result = await monitorPanes({
649
+ leaderPaneId,
650
+ workerPaneIds,
651
+ sessionLabel: flagValue(argv, "--session-label"),
652
+ config: {
653
+ pollIntervalMs,
654
+ readySamples,
655
+ minObservationMs,
656
+ timeoutMs,
657
+ captureLines,
658
+ },
659
+ });
660
+ return json
661
+ ? { ok: result.ok, exitCode: result.ok ? 0 : 1, output: result }
662
+ : { ok: result.ok, exitCode: result.ok ? 0 : 1, message: `team monitor-panes ${result.reason} events=${result.events.length}` };
663
+ }
619
664
  if (command === "api") {
620
665
  const sub = value;
621
666
  const inputRaw = flagValue(argv, "--input");