agentxchain 0.8.1 → 0.8.3

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agentxchain
2
2
 
3
- CLI for multi-agent coordination in your IDE. Define a team of AI agents, let them take turns building your project via shared state and lifecycle hooks.
3
+ CLI for multi-agent coordination in your IDE. Define a team of AI agents, let them take turns building your project each in its own IDE window.
4
4
 
5
5
  ## Install
6
6
 
@@ -17,59 +17,139 @@ npx agentxchain init
17
17
  ## Quick start
18
18
 
19
19
  ```bash
20
- agentxchain init # create project with agents + hooks
21
- cd my-project/ && code . # open in VS Code / Cursor
22
- # Select an agent from the Chat dropdown (auto-discovered from .github/agents/)
23
- agentxchain release # release human lock to begin turns
20
+ # 1. Create a project (interactive template selection)
21
+ agentxchain init
22
+
23
+ # 2. PM-first kickoff (human + PM align scope first)
24
+ cd my-project/
25
+ agentxchain start --agent pm
26
+
27
+ # 3. Launch remaining agents after planning is clear
28
+ agentxchain start --remaining
29
+
30
+ # 4. Start supervisor (watch + auto-nudge)
31
+ agentxchain supervise --autonudge
32
+
33
+ # 5. Release the human lock — agents start claiming turns
34
+ agentxchain release
24
35
  ```
25
36
 
26
- The `Stop` hook acts as referee: when an agent finishes, it determines the next agent and hands off automatically. No polling process needed.
37
+ Each agent runs in its own Cursor window with a self-polling loop. Agents check `lock.json` every 60 seconds, claim when it's their turn, do their work, release, and go back to waiting. `supervise --autonudge` handles watch + nudging automatically.
27
38
 
28
39
  ## Commands
29
40
 
30
41
  | Command | What it does |
31
42
  |---------|-------------|
32
- | `init` | Create project folder with agents, hooks, protocol files, and templates |
33
- | `generate` | Regenerate VS Code agent files (`.agent.md`, hooks) from `agentxchain.json` |
34
- | `start` | Show agent setup instructions for your IDE |
35
- | `status` | Show lock, phase, agents |
36
- | `claim` | Human takes control |
43
+ | `init` | Create project folder with agents, protocol files, and templates |
44
+ | `start` | Open a Cursor window per agent + copy prompts to clipboard |
45
+ | `supervise` | Run watcher and optional AppleScript auto-nudge together |
46
+ | `generate` | Regenerate agent files from `agentxchain.json` |
47
+ | `status` | Show lock holder, phase, turn number, agents |
48
+ | `doctor` | Validate local setup (tools, trigger flow, accessibility checks) |
49
+ | `claim` | Human takes control (agents stop claiming) |
37
50
  | `release` | Hand lock back to agents |
38
51
  | `stop` | Terminate running Claude Code agent sessions |
39
- | `watch` | Fallback referee for non-IDE environments |
52
+ | `watch` | Optional: TTL safety net + status logging |
40
53
  | `config` | View/edit config, add/remove agents, change rules |
41
54
  | `update` | Self-update CLI from npm |
42
55
 
56
+ ### IDE options
57
+
58
+ ```bash
59
+ agentxchain start # Cursor (default) — one window per agent
60
+ agentxchain start --ide vscode # VS Code — uses .agent.md custom agents + hooks
61
+ agentxchain start --ide claude-code # Claude Code — spawns CLI processes
62
+ ```
63
+
43
64
  ### Additional flags
44
65
 
45
66
  ```bash
67
+ agentxchain start --agent pm # launch only one specific agent
68
+ agentxchain start --remaining # launch all agents except PM (PM-first flow)
69
+ agentxchain start --dry-run # preview agents without launching
46
70
  agentxchain watch --daemon # run watch in background
71
+ agentxchain supervise --autonudge # run watch + AppleScript nudge loop
72
+ agentxchain supervise --autonudge --send # auto-press Enter after paste
47
73
  agentxchain release --force # force-release non-human holder lock
48
74
  ```
49
75
 
76
+ ## macOS auto-nudge (AppleScript)
77
+
78
+ If you want the next agent chat to be nudged automatically when turn changes, use the built-in AppleScript helper.
79
+
80
+ 1) Keep watcher running in your project:
81
+
82
+ ```bash
83
+ agentxchain watch
84
+ # or use the combined command:
85
+ agentxchain supervise --autonudge
86
+ ```
87
+
88
+ 2) In another terminal (from `cli/`), start auto-nudge:
89
+
90
+ ```bash
91
+ bash scripts/run-autonudge.sh --project "/absolute/path/to/your-project"
92
+ ```
93
+
94
+ By default this is **paste-only** (safe mode): it opens chat and pastes the nudge message, but does not press Enter.
95
+
96
+ 3) Enable auto-send once confirmed:
97
+
98
+ ```bash
99
+ bash scripts/run-autonudge.sh --project "/absolute/path/to/your-project" --send
100
+ ```
101
+
102
+ Stop it anytime:
103
+
104
+ ```bash
105
+ bash scripts/stop-autonudge.sh
106
+ ```
107
+
108
+ Notes:
109
+ - Requires macOS (`osascript`) and `jq` (`brew install jq`)
110
+ - Grant Accessibility permissions to Terminal and Cursor
111
+ - The script watches `.agentxchain-trigger.json`, which is written by `agentxchain watch`
112
+ - `run-autonudge.sh` now requires watch to be running first
113
+
50
114
  ## How it works
51
115
 
52
- `agentxchain init` generates native VS Code agent files:
116
+ ### Cursor mode (default)
117
+
118
+ 1. `agentxchain start` opens a **separate Cursor window** for each agent
119
+ 2. Each window gets a unique prompt copied to clipboard
120
+ 3. Recommended first-run flow: `start --agent pm` (kickoff), then `start --remaining`
121
+ 4. Agent prompts include a self-polling loop: read `lock.json` → check if it's my turn → claim → work → release → sleep 60s → repeat
122
+ 5. Agents know their rotation order from `agentxchain.json` and only claim when the previous agent released
123
+ 6. Human can `claim` to pause and `release` to resume anytime
124
+
125
+ ### VS Code mode
126
+
127
+ 1. `agentxchain init` generates `.github/agents/*.agent.md` (VS Code custom agents) and `.github/hooks/` (lifecycle hooks)
128
+ 2. VS Code auto-discovers agents in the Chat dropdown
129
+ 3. The `Stop` hook acts as referee — hands off to next agent automatically
53
130
 
54
- - `.github/agents/*.agent.md` — custom agents (auto-discovered by VS Code / Cursor Chat)
55
- - `.github/hooks/agentxchain.json` — lifecycle hooks (Stop = referee, SessionStart = context injection)
56
- - `scripts/agentxchain-*.sh` — hook shell scripts
131
+ ### Turn rotation
57
132
 
58
- When an agent finishes its response, the Stop hook reads `lock.json`, determines the next agent, and hands off automatically.
133
+ Agents follow a round-robin order defined in `agentxchain.json`:
134
+ - PM waits for: lock free + last released by `human`, `null`, or last agent in rotation
135
+ - Dev waits for: lock free + last released by `pm`
136
+ - QA waits for: lock free + last released by `dev`
137
+ - And so on...
59
138
 
60
139
  ## Key features
61
140
 
62
- - **Native VS Code agents** — `.agent.md` files, lifecycle hooks, handoffs
63
- - **Works in any VS Code fork** Cursor, VS Code, Windsurf, etc.
64
- - **Stop hook referee** — deterministic turn coordination via lifecycle hooks
141
+ - **One window per agent** — each agent has its own Cursor window and chat session
142
+ - **Self-polling coordination** agents check `lock.json` every 60s, no external process needed
143
+ - **Works in Cursor, VS Code, Claude Code** — adapters for each IDE
65
144
  - **User-defined teams** — any number of agents, any roles
66
145
  - **No API keys or cloud required** — everything runs locally
67
146
  - **Human-in-the-loop** — claim/release to intervene anytime
68
147
  - **Team templates** — SaaS MVP, Landing Page, Bug Squad, API Builder, Refactor Team
148
+ - **Lock TTL** — `watch` can force-release stale locks as a safety net
69
149
 
70
150
  ## VS Code extension (optional)
71
151
 
72
- For a richer UI experience, install the extension:
152
+ For a richer UI in VS Code:
73
153
 
74
154
  ```bash
75
155
  code --install-extension cli/vscode-extension/agentxchain-0.1.0.vsix
@@ -13,6 +13,8 @@ import { updateCommand } from '../src/commands/update.js';
13
13
  import { watchCommand } from '../src/commands/watch.js';
14
14
  import { claimCommand, releaseCommand } from '../src/commands/claim.js';
15
15
  import { generateCommand } from '../src/commands/generate.js';
16
+ import { doctorCommand } from '../src/commands/doctor.js';
17
+ import { superviseCommand } from '../src/commands/supervise.js';
16
18
 
17
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
20
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
@@ -41,6 +43,7 @@ program
41
43
  .description('Launch agents in your IDE')
42
44
  .option('--ide <ide>', 'Target IDE: cursor, vscode, claude-code', 'cursor')
43
45
  .option('--agent <id>', 'Launch a specific agent only')
46
+ .option('--remaining', 'Launch all remaining agents except PM (for PM-first flow)')
44
47
  .option('--dry-run', 'Print what would be launched without doing it')
45
48
  .action(startCommand);
46
49
 
@@ -69,6 +72,14 @@ program
69
72
  .option('--daemon', 'Run in background mode')
70
73
  .action(watchCommand);
71
74
 
75
+ program
76
+ .command('supervise')
77
+ .description('Run watch loop and optional auto-nudge together')
78
+ .option('--autonudge', 'Start AppleScript auto-nudge alongside watch')
79
+ .option('--send', 'Auto-send nudges (default is paste-only)')
80
+ .option('--interval <seconds>', 'Auto-nudge poll interval in seconds', '3')
81
+ .action(superviseCommand);
82
+
72
83
  program
73
84
  .command('claim')
74
85
  .description('Claim the lock as a human (take control)')
@@ -86,4 +97,9 @@ program
86
97
  .description('Update agentxchain CLI to the latest version')
87
98
  .action(updateCommand);
88
99
 
100
+ program
101
+ .command('doctor')
102
+ .description('Check local environment and first-run readiness')
103
+ .action(doctorCommand);
104
+
89
105
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "scripts/",
12
13
  "README.md"
13
14
  ],
14
15
  "scripts": {
@@ -0,0 +1,107 @@
1
+ property projectRoot : ""
2
+ property pollSeconds : 3
3
+ property autoSend : false
4
+
5
+ on run argv
6
+ if (count of argv) < 1 then
7
+ error "Usage: osascript agentxchain-autonudge.applescript <projectRoot> [autoSend:true|false] [pollSeconds]"
8
+ end if
9
+
10
+ set projectRoot to item 1 of argv
11
+
12
+ if (count of argv) >= 2 then
13
+ set sendArg to item 2 of argv
14
+ if sendArg is "true" then
15
+ set autoSend to true
16
+ else
17
+ set autoSend to false
18
+ end if
19
+ end if
20
+
21
+ if (count of argv) >= 3 then
22
+ try
23
+ set pollSeconds to (item 3 of argv) as integer
24
+ on error
25
+ set pollSeconds to 3
26
+ end try
27
+ end if
28
+
29
+ set triggerPath to projectRoot & "/.agentxchain-trigger.json"
30
+ set statePath to projectRoot & "/.agentxchain-autonudge.state"
31
+
32
+ repeat
33
+ try
34
+ set hasTrigger to do shell script "test -f " & quoted form of triggerPath & " && echo yes || echo no"
35
+ if hasTrigger is "yes" then
36
+ set agentId to do shell script "jq -r '.agent // empty' " & quoted form of triggerPath
37
+ set turnNum to do shell script "jq -r '.turn_number // 0' " & quoted form of triggerPath
38
+
39
+ if agentId is not "" then
40
+ set dispatchKey to agentId & ":" & turnNum
41
+ set lastKey to do shell script "test -f " & quoted form of statePath & " && cat " & quoted form of statePath & " || echo ''"
42
+
43
+ if dispatchKey is not lastKey then
44
+ my nudgeAgent(agentId, turnNum)
45
+ do shell script "printf %s " & quoted form of dispatchKey & " > " & quoted form of statePath
46
+ end if
47
+ end if
48
+ end if
49
+ end try
50
+
51
+ delay pollSeconds
52
+ end repeat
53
+ end run
54
+
55
+ on nudgeAgent(agentId, turnNum)
56
+ set nudgeText to "Hey " & agentId & ", it is your turn now (turn " & turnNum & "). Read lock.json, claim the lock, check state.md + history.jsonl + planning docs, do your work, and release lock."
57
+ set the clipboard to nudgeText
58
+
59
+ tell application "Cursor" to activate
60
+ delay 0.5
61
+ my focusAgentWindow(agentId)
62
+ delay 0.2
63
+
64
+ tell application "System Events"
65
+ if not (exists process "Cursor") then return
66
+
67
+ tell process "Cursor"
68
+ set frontmost to true
69
+ keystroke "l" using {command down}
70
+ delay 0.2
71
+ keystroke "v" using {command down}
72
+ if autoSend then
73
+ delay 0.15
74
+ key code 36
75
+ end if
76
+ end tell
77
+ end tell
78
+
79
+ do shell script "osascript -e " & quoted form of ("display notification \"Nudged " & agentId & " for turn " & turnNum & "\" with title \"AgentXchain\"")
80
+ end nudgeAgent
81
+
82
+ on focusAgentWindow(agentId)
83
+ tell application "System Events"
84
+ if not (exists process "Cursor") then return
85
+ tell process "Cursor"
86
+ set frontmost to true
87
+
88
+ set didRaise to false
89
+ repeat with w in windows
90
+ try
91
+ set wn to name of w as text
92
+ if wn contains agentId then
93
+ perform action "AXRaise" of w
94
+ set didRaise to true
95
+ exit repeat
96
+ end if
97
+ end try
98
+ end repeat
99
+
100
+ if didRaise is false then
101
+ try
102
+ perform action "AXRaise" of window 1
103
+ end try
104
+ end if
105
+ end tell
106
+ end tell
107
+ end focusAgentWindow
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ # Build standalone binaries for macOS and Linux using Bun.
3
+ # Requires: bun (install via `brew install oven-sh/bun/bun`)
4
+ set -e
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ CLI_DIR="${SCRIPT_DIR}/.."
8
+ cd "$CLI_DIR"
9
+
10
+ VERSION=$(node -e "console.log(require('./package.json').version)")
11
+ DIST="$CLI_DIR/dist"
12
+ mkdir -p "$DIST"
13
+
14
+ echo "Building agentxchain v${VERSION}..."
15
+ echo ""
16
+
17
+ echo "=== macOS arm64 ==="
18
+ bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile="$DIST/agentxchain-macos-arm64"
19
+ echo " → $DIST/agentxchain-macos-arm64"
20
+
21
+ echo ""
22
+ echo "=== macOS x64 ==="
23
+ bun build bin/agentxchain.js --compile --target=bun-darwin-x64 --outfile="$DIST/agentxchain-macos-x64"
24
+ echo " → $DIST/agentxchain-macos-x64"
25
+
26
+ echo ""
27
+ echo "=== Linux x64 ==="
28
+ bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile="$DIST/agentxchain-linux-x64"
29
+ echo " → $DIST/agentxchain-linux-x64"
30
+
31
+ echo ""
32
+ echo "Creating tarballs..."
33
+ cd "$DIST"
34
+ tar -czf "agentxchain-${VERSION}-macos-arm64.tar.gz" agentxchain-macos-arm64
35
+ tar -czf "agentxchain-${VERSION}-macos-x64.tar.gz" agentxchain-macos-x64
36
+ tar -czf "agentxchain-${VERSION}-linux-x64.tar.gz" agentxchain-linux-x64
37
+
38
+ echo ""
39
+ echo "Done. Upload these to a GitHub release, then update the Homebrew formula with the URLs and SHA256 hashes."
40
+ echo ""
41
+ echo "SHA256 hashes:"
42
+ shasum -a 256 *.tar.gz
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env bash
2
+ # Publish a new version of agentxchain to npm.
3
+ # Usage:
4
+ # bash scripts/publish-npm.sh # bump patch + publish
5
+ # bash scripts/publish-npm.sh minor # bump minor + publish
6
+ # bash scripts/publish-npm.sh major # bump major + publish
7
+ # bash scripts/publish-npm.sh 0.5.0 # set explicit version + publish
8
+ # bash scripts/publish-npm.sh patch --dry-run
9
+ set -euo pipefail
10
+
11
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12
+ CLI_DIR="${SCRIPT_DIR}/.."
13
+ cd "$CLI_DIR"
14
+
15
+ if [[ ! -f "package.json" ]]; then
16
+ echo "Error: package.json not found in $CLI_DIR"
17
+ exit 1
18
+ fi
19
+
20
+ PACKAGE_NAME="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).name)")"
21
+ BUMP="${1:-patch}"
22
+ DRY_RUN="${2:-}"
23
+
24
+ if [[ "$BUMP" != "patch" && "$BUMP" != "minor" && "$BUMP" != "major" && ! "$BUMP" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
25
+ echo "Error: invalid version bump '$BUMP'. Use patch|minor|major|x.y.z"
26
+ exit 1
27
+ fi
28
+
29
+ # Load NPM_TOKEN from agentXchain.dev/.env only.
30
+ PARENT_ENV_FILE="${CLI_DIR}/../.env"
31
+ if [[ -f "${PARENT_ENV_FILE}" ]]; then
32
+ set -a
33
+ # shellcheck disable=SC1090
34
+ source "${PARENT_ENV_FILE}"
35
+ set +a
36
+ fi
37
+
38
+ CURRENT_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version)")"
39
+ echo "Current version: ${CURRENT_VERSION}"
40
+ echo "Package name: ${PACKAGE_NAME}"
41
+ echo "Requested bump: ${BUMP}"
42
+ echo ""
43
+
44
+ if [[ "$DRY_RUN" == "--dry-run" ]]; then
45
+ echo "Dry run mode. No files changed."
46
+ exit 0
47
+ fi
48
+
49
+ # Preflight auth/ownership checks before version bump.
50
+ echo "Running npm preflight checks..."
51
+ if [[ -n "${NPM_TOKEN:-}" ]]; then
52
+ if ! npm whoami --//registry.npmjs.org/:_authToken="${NPM_TOKEN}" >/dev/null 2>&1; then
53
+ echo "Error: npm auth failed with NPM_TOKEN from agentxchain.dev/.env"
54
+ echo "Verify NPM_TOKEN is valid and has package publish permissions."
55
+ exit 1
56
+ fi
57
+ NPM_USER="$(npm whoami --//registry.npmjs.org/:_authToken="${NPM_TOKEN}")"
58
+ else
59
+ if ! npm whoami >/dev/null 2>&1; then
60
+ echo "Error: npm auth missing. Run: npm login or set NPM_TOKEN in agentxchain.dev/.env"
61
+ exit 1
62
+ fi
63
+ NPM_USER="$(npm whoami)"
64
+ fi
65
+ echo "npm user: ${NPM_USER}"
66
+
67
+ if npm view "${PACKAGE_NAME}" version >/dev/null 2>&1; then
68
+ # Existing package: confirm owner access.
69
+ if ! npm owner ls "${PACKAGE_NAME}" | grep -q "^${NPM_USER} "; then
70
+ echo "Error: npm user '${NPM_USER}' is not an owner of '${PACKAGE_NAME}'."
71
+ echo "Ask an existing owner to run: npm owner add ${NPM_USER} ${PACKAGE_NAME}"
72
+ exit 1
73
+ fi
74
+ echo "ownership: ok"
75
+ else
76
+ echo "package status: new package or not visible to this auth context"
77
+ fi
78
+ echo ""
79
+
80
+ echo "Bumping package version..."
81
+ npm version "$BUMP" --no-git-tag-version
82
+
83
+ NEW_VERSION="$(node -e "console.log(JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version)")"
84
+ echo "New version: ${NEW_VERSION}"
85
+ echo ""
86
+
87
+ echo "Publishing to npm..."
88
+ if [[ -n "${NPM_TOKEN:-}" ]]; then
89
+ npm publish --access public --//registry.npmjs.org/:_authToken="${NPM_TOKEN}"
90
+ else
91
+ npm publish --access public
92
+ fi
93
+
94
+ echo ""
95
+ echo "Done. Published agentxchain@${NEW_VERSION}"
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ APPLESCRIPT_PATH="${SCRIPT_DIR}/agentxchain-autonudge.applescript"
6
+
7
+ PROJECT_ROOT="$(pwd)"
8
+ AUTO_SEND="false"
9
+ INTERVAL_SECONDS="3"
10
+
11
+ usage() {
12
+ cat <<'EOF'
13
+ Usage: bash scripts/run-autonudge.sh [options]
14
+
15
+ Options:
16
+ --project <path> Project root that contains lock.json
17
+ --send Auto-send message (presses Enter after paste)
18
+ --paste-only Paste only (default, no Enter)
19
+ --interval <seconds> Poll interval in seconds (default: 3)
20
+ -h, --help Show help
21
+
22
+ Example:
23
+ bash scripts/run-autonudge.sh --project "$(pwd)" --send --interval 2
24
+ EOF
25
+ }
26
+
27
+ while [[ $# -gt 0 ]]; do
28
+ case "$1" in
29
+ --project)
30
+ PROJECT_ROOT="$2"
31
+ shift 2
32
+ ;;
33
+ --send)
34
+ AUTO_SEND="true"
35
+ shift
36
+ ;;
37
+ --paste-only)
38
+ AUTO_SEND="false"
39
+ shift
40
+ ;;
41
+ --interval)
42
+ INTERVAL_SECONDS="$2"
43
+ shift 2
44
+ ;;
45
+ -h|--help)
46
+ usage
47
+ exit 0
48
+ ;;
49
+ *)
50
+ echo "Unknown option: $1"
51
+ usage
52
+ exit 1
53
+ ;;
54
+ esac
55
+ done
56
+
57
+ if ! command -v osascript >/dev/null 2>&1; then
58
+ echo "osascript not found. This script requires macOS."
59
+ exit 1
60
+ fi
61
+
62
+ if ! command -v jq >/dev/null 2>&1; then
63
+ echo "jq not found. Install with: brew install jq"
64
+ exit 1
65
+ fi
66
+
67
+ if [[ ! -f "${PROJECT_ROOT}/lock.json" ]]; then
68
+ echo "lock.json not found in: ${PROJECT_ROOT}"
69
+ echo "Run this from your AgentXchain project root, or pass --project <path>."
70
+ exit 1
71
+ fi
72
+
73
+ if [[ ! -f "${APPLESCRIPT_PATH}" ]]; then
74
+ echo "AppleScript not found: ${APPLESCRIPT_PATH}"
75
+ exit 1
76
+ fi
77
+
78
+ WATCH_READY="false"
79
+ for _ in {1..10}; do
80
+ if pgrep -f "agentxchain.*watch" >/dev/null 2>&1; then
81
+ WATCH_READY="true"
82
+ break
83
+ fi
84
+ sleep 1
85
+ done
86
+
87
+ if [[ "${WATCH_READY}" != "true" ]]; then
88
+ echo "watch process not detected."
89
+ echo "Run one of these first:"
90
+ echo " agentxchain watch"
91
+ echo " agentxchain supervise --autonudge"
92
+ exit 1
93
+ fi
94
+
95
+ if [[ ! -f "${PROJECT_ROOT}/.agentxchain-trigger.json" ]]; then
96
+ echo "warning: .agentxchain-trigger.json does not exist yet."
97
+ echo "auto-nudge will start, but nudges begin only after watch writes a trigger."
98
+ fi
99
+
100
+ echo ""
101
+ echo "AgentXchain auto-nudge starting..."
102
+ echo "Project: ${PROJECT_ROOT}"
103
+ echo "Mode: $( [[ "${AUTO_SEND}" == "true" ]] && echo "auto-send" || echo "paste-only" )"
104
+ echo "Interval: ${INTERVAL_SECONDS}s"
105
+ echo ""
106
+ echo "Requirements:"
107
+ echo "- Keep 'agentxchain watch' running in another terminal."
108
+ echo "- Grant Accessibility permission to Terminal and Cursor."
109
+ echo ""
110
+
111
+ osascript "${APPLESCRIPT_PATH}" "${PROJECT_ROOT}" "${AUTO_SEND}" "${INTERVAL_SECONDS}"
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ PIDS="$(pgrep -f "agentxchain-autonudge.applescript" || true)"
5
+
6
+ if [[ -z "${PIDS}" ]]; then
7
+ echo "No running auto-nudge process found."
8
+ exit 0
9
+ fi
10
+
11
+ echo "Stopping auto-nudge process(es): ${PIDS}"
12
+ kill ${PIDS}
13
+ echo "Stopped."
@@ -1,5 +1,5 @@
1
1
  import { execSync } from 'child_process';
2
- import { writeFileSync } from 'fs';
2
+ import { writeFileSync, mkdirSync, existsSync, symlinkSync, lstatSync, unlinkSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
@@ -9,51 +9,106 @@ export async function launchCursorLocal(config, root, opts) {
9
9
  const agents = filterAgents(config, opts.agent);
10
10
  const agentEntries = Object.entries(agents);
11
11
  const total = agentEntries.length;
12
+ const selectedAgent = opts.agent ? agents[opts.agent] : null;
13
+ const isPmKickoff = !!selectedAgent && isPmLike(opts.agent, selectedAgent) && !opts.remaining;
12
14
 
13
- console.log(chalk.bold(` Opening ${total} Cursor windows for: ${Object.keys(agents).join(', ')}`));
15
+ console.log(chalk.bold(` Setting up ${total} agents: ${Object.keys(agents).join(', ')}`));
16
+ if (isPmKickoff) {
17
+ console.log(chalk.dim(' PM kickoff mode: start with human-PM planning before launching the full team.'));
18
+ }
14
19
  console.log('');
15
20
 
16
21
  const promptDir = join(root, '.agentxchain-prompts');
17
- try { execSync(`mkdir -p "${promptDir}"`); } catch {}
22
+ mkdirSync(promptDir, { recursive: true });
23
+
24
+ // Save all prompts first
25
+ for (const [id, agent] of agentEntries) {
26
+ const prompt = isPmKickoff
27
+ ? generateKickoffPrompt(id, agent, config)
28
+ : generatePollingPrompt(id, agent, config);
29
+ writeFileSync(join(promptDir, `${id}.prompt.md`), prompt);
30
+ }
31
+
32
+ // Create per-agent symlinked workspace folders so Cursor opens separate windows
33
+ const workspacesDir = join(root, '.agentxchain-workspaces');
34
+ mkdirSync(workspacesDir, { recursive: true });
18
35
 
19
36
  for (let i = 0; i < agentEntries.length; i++) {
20
37
  const [id, agent] = agentEntries[i];
21
- const prompt = generatePollingPrompt(id, agent, config);
22
- const promptFile = join(promptDir, `${id}.prompt.md`);
23
- writeFileSync(promptFile, prompt);
38
+ const prompt = isPmKickoff
39
+ ? generateKickoffPrompt(id, agent, config)
40
+ : generatePollingPrompt(id, agent, config);
41
+
42
+ // Create symlink: .agentxchain-workspaces/<id> -> project root
43
+ const agentWorkspace = join(workspacesDir, id);
44
+ try {
45
+ if (existsSync(agentWorkspace)) {
46
+ const stat = lstatSync(agentWorkspace);
47
+ if (stat.isSymbolicLink()) unlinkSync(agentWorkspace);
48
+ }
49
+ if (!existsSync(agentWorkspace)) {
50
+ symlinkSync(root, agentWorkspace, 'dir');
51
+ }
52
+ } catch {}
24
53
 
25
- console.log(` ${chalk.cyan(`Window ${i + 1}/${total}:`)} ${chalk.bold(id)} — ${agent.name}`);
54
+ console.log(chalk.cyan(` ─── Agent ${i + 1}/${total}: ${chalk.bold(id)} — ${agent.name} ───`));
55
+ console.log('');
26
56
 
27
57
  copyToClipboard(prompt);
28
- console.log(chalk.green(` Prompt copied to clipboard.`));
29
- console.log(chalk.dim(` Also saved to: .agentxchain-prompts/${id}.prompt.md`));
58
+ console.log(chalk.green(`Prompt copied to clipboard.`));
59
+ console.log(chalk.dim(` Saved to: .agentxchain-prompts/${id}.prompt.md`));
30
60
 
31
- tryOpenCursor(root);
61
+ // Open a separate Cursor window using the symlinked path
62
+ openCursorWindow(agentWorkspace);
63
+ console.log(chalk.dim(` Cursor window opened for ${id}.`));
64
+
65
+ console.log('');
66
+ console.log(` ${chalk.bold('In the new Cursor window:')}`);
67
+ console.log(` 1. Open chat (${chalk.bold('Cmd+L')})`);
68
+ console.log(` 2. Paste the prompt (${chalk.bold('Cmd+V')})`);
69
+ if (isPmKickoff) {
70
+ console.log(` 3. ${chalk.bold('Select Agent mode')} and discuss requirements with PM`);
71
+ console.log(` 4. Update planning docs together (PROJECT / REQUIREMENTS / ROADMAP)`);
72
+ } else {
73
+ console.log(` 3. ${chalk.bold('Select Agent mode')} (not Ask/Edit)`);
74
+ console.log(` 4. Send it (${chalk.bold('Enter')})`);
75
+ }
32
76
 
33
77
  if (i < agentEntries.length - 1) {
34
78
  console.log('');
35
79
  await inquirer.prompt([{
36
80
  type: 'input',
37
81
  name: 'ready',
38
- message: chalk.dim(` Paste prompt in Cursor chat (Cmd+V), send it, then press Enter here for the next agent...`)
82
+ message: ` Done? Press Enter to open next window (${agentEntries[i + 1][0]})...`
39
83
  }]);
84
+ console.log('');
40
85
  } else {
41
86
  console.log('');
42
- console.log(chalk.dim(` Paste this last prompt in Cursor chat (Cmd+V) and send it.`));
87
+ console.log(chalk.dim(` Last agent. Paste and send, then come back here.`));
88
+ console.log('');
43
89
  }
44
90
  }
45
91
 
92
+ console.log(chalk.green(` ✓ All ${total} agents launched in separate Cursor windows.`));
46
93
  console.log('');
47
- console.log(chalk.green(' All agent prompts ready.'));
94
+ console.log(` ${chalk.cyan('Now run:')}`);
95
+ if (isPmKickoff) {
96
+ console.log(` ${chalk.bold('agentxchain start --remaining')} ${chalk.dim('# launch the rest of the team when PM plan is ready')}`);
97
+ console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# start watch + auto-nudge')}`);
98
+ console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock to begin team turns')}`);
99
+ } else {
100
+ console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# recommended: watch + auto-nudge in one command')}`);
101
+ console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock — agents start claiming turns')}`);
102
+ }
48
103
  console.log('');
49
- console.log(` ${chalk.cyan('Next:')}`);
50
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock — agents start claiming turns')}`);
51
- console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# optional: TTL safety net + status logging')}`);
104
+ console.log(` ${chalk.dim('Other commands:')}`);
52
105
  console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# check who holds the lock')}`);
53
106
  console.log(` ${chalk.bold('agentxchain claim')} ${chalk.dim('# pause agents and take control')}`);
107
+ console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# watcher / trigger writer')}`);
108
+ console.log(` ${chalk.bold('agentxchain doctor')} ${chalk.dim('# check local setup + trigger health')}`);
54
109
  console.log('');
55
110
  console.log(chalk.dim(' Agents self-coordinate via lock.json polling (sleep 60s between checks).'));
56
- console.log(chalk.dim(' Prompts saved in .agentxchain-prompts/ if you need to re-paste.'));
111
+ console.log(chalk.dim(' Re-paste a prompt: cat .agentxchain-prompts/<agent>.prompt.md | pbcopy'));
57
112
  console.log('');
58
113
  }
59
114
 
@@ -82,14 +137,45 @@ function copyToClipboard(text) {
82
137
  return false;
83
138
  }
84
139
 
85
- function tryOpenCursor(root) {
140
+ function openCursorWindow(folderPath) {
86
141
  try {
87
142
  if (process.platform === 'darwin') {
88
- execSync(`open -na "Cursor" --args "${root}"`, { stdio: 'ignore' });
143
+ execSync(`open -na "Cursor" --args "${folderPath}"`, { stdio: 'ignore' });
89
144
  return;
90
145
  }
91
- execSync(`cursor "${root}"`, { stdio: 'ignore' });
92
- } catch {
93
- // Cursor not found or can't open — user will open manually
94
- }
146
+ execSync(`cursor --new-window "${folderPath}"`, { stdio: 'ignore' });
147
+ } catch {}
148
+ }
149
+
150
+ function isPmLike(agentId, agentDef) {
151
+ if (agentId === 'pm') return true;
152
+ const name = String(agentDef?.name || '').toLowerCase();
153
+ return name.includes('product manager');
154
+ }
155
+
156
+ function generateKickoffPrompt(agentId, agentDef, config) {
157
+ return `You are "${agentId}" — ${agentDef.name}.
158
+
159
+ This is PM kickoff mode. Your job now is to collaborate with the human and finalize scope before autonomous turns begin.
160
+
161
+ Actions:
162
+ 1) Read:
163
+ - .planning/PROJECT.md
164
+ - .planning/REQUIREMENTS.md
165
+ - .planning/ROADMAP.md
166
+ - state.md
167
+ - lock.json
168
+ 2) Ask the human focused product questions until scope is clear:
169
+ - target user
170
+ - top pain point
171
+ - core workflow
172
+ - MVP boundary
173
+ - success metric
174
+ 3) Update planning docs with concrete acceptance criteria.
175
+ 4) Do NOT start round-robin agent handoffs yet.
176
+
177
+ Context:
178
+ - Project: ${config.project}
179
+ - Human currently holds lock.
180
+ - Once planning is approved, human will launch remaining agents and run release.`;
95
181
  }
@@ -0,0 +1,132 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { execSync } from 'child_process';
3
+ import { join } from 'path';
4
+ import chalk from 'chalk';
5
+ import { loadConfig, loadLock } from '../lib/config.js';
6
+
7
+ export async function doctorCommand() {
8
+ const result = loadConfig();
9
+ if (!result) {
10
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
11
+ process.exit(1);
12
+ }
13
+
14
+ const { root, config } = result;
15
+ const lock = loadLock(root);
16
+ const checks = [];
17
+
18
+ checks.push(checkFile('agentxchain.json', existsSync(join(root, 'agentxchain.json')), 'Project config exists'));
19
+ checks.push(checkFile('lock.json', !!lock, 'Lock file exists'));
20
+ checks.push(checkBinary('cursor', 'Cursor CLI available (optional for non-macOS launch)'));
21
+ checks.push(checkBinary('jq', 'jq installed (required for auto-nudge)'));
22
+ checks.push(checkBinary('osascript', 'osascript available (required for auto-nudge, macOS)'));
23
+ checks.push(checkPm(config));
24
+ checks.push(checkWatchProcess());
25
+ checks.push(checkTrigger(root));
26
+ checks.push(checkAccessibility());
27
+
28
+ console.log('');
29
+ console.log(chalk.bold(' AgentXchain Doctor'));
30
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
31
+ console.log(chalk.dim(` Project root: ${root}`));
32
+ console.log('');
33
+
34
+ for (const c of checks) {
35
+ const badge = c.level === 'pass'
36
+ ? chalk.green('PASS')
37
+ : c.level === 'warn'
38
+ ? chalk.yellow('WARN')
39
+ : chalk.red('FAIL');
40
+ console.log(` ${badge} ${c.name}`);
41
+ if (c.detail) console.log(` ${chalk.dim(c.detail)}`);
42
+ }
43
+
44
+ const failCount = checks.filter(c => c.level === 'fail').length;
45
+ const warnCount = checks.filter(c => c.level === 'warn').length;
46
+
47
+ console.log('');
48
+ if (failCount === 0 && warnCount === 0) {
49
+ console.log(chalk.green(' ✓ Environment looks ready.'));
50
+ } else if (failCount === 0) {
51
+ console.log(chalk.yellow(` Ready with warnings (${warnCount}).`));
52
+ } else {
53
+ console.log(chalk.red(` Not ready: ${failCount} blocking issue(s).`));
54
+ }
55
+ console.log('');
56
+ }
57
+
58
+ function checkFile(name, ok, detail) {
59
+ return {
60
+ name,
61
+ level: ok ? 'pass' : 'fail',
62
+ detail: ok ? detail : `${name} is missing`
63
+ };
64
+ }
65
+
66
+ function checkBinary(cmd, detail) {
67
+ try {
68
+ execSync(`command -v ${cmd}`, { stdio: 'ignore' });
69
+ return { name: cmd, level: 'pass', detail };
70
+ } catch {
71
+ return { name: cmd, level: 'warn', detail: `${cmd} not found in PATH` };
72
+ }
73
+ }
74
+
75
+ function checkPm(config) {
76
+ if (config.agents?.pm) {
77
+ return { name: 'PM agent', level: 'pass', detail: 'pm exists in agentxchain.json' };
78
+ }
79
+ const hasPmLike = Object.values(config.agents || {}).some(a => String(a?.name || '').toLowerCase().includes('product manager'));
80
+ if (hasPmLike) {
81
+ return { name: 'PM agent', level: 'pass', detail: 'Product Manager role detected' };
82
+ }
83
+ return { name: 'PM agent', level: 'warn', detail: 'No explicit PM agent. PM-first onboarding will be less clear.' };
84
+ }
85
+
86
+ function checkWatchProcess() {
87
+ try {
88
+ execSync('pgrep -f "agentxchain.*watch" >/dev/null', { stdio: 'ignore' });
89
+ return { name: 'watch process', level: 'pass', detail: 'watch appears to be running' };
90
+ } catch {
91
+ return { name: 'watch process', level: 'warn', detail: 'watch not running (start with `agentxchain watch` or `agentxchain supervise --autonudge`)' };
92
+ }
93
+ }
94
+
95
+ function checkTrigger(root) {
96
+ const triggerPath = join(root, '.agentxchain-trigger.json');
97
+ if (!existsSync(triggerPath)) {
98
+ return { name: 'trigger file', level: 'warn', detail: '.agentxchain-trigger.json not found yet' };
99
+ }
100
+
101
+ try {
102
+ const raw = JSON.parse(readFileSync(triggerPath, 'utf8'));
103
+ const triggeredAt = new Date(raw.triggered_at).getTime();
104
+ const ageMs = Date.now() - triggeredAt;
105
+ if (Number.isFinite(triggeredAt) && ageMs <= 120000) {
106
+ return { name: 'trigger file', level: 'pass', detail: `fresh trigger (${Math.round(ageMs / 1000)}s ago)` };
107
+ }
108
+ return { name: 'trigger file', level: 'warn', detail: 'trigger file is stale; lock may not be advancing' };
109
+ } catch {
110
+ return { name: 'trigger file', level: 'warn', detail: 'trigger file exists but is invalid JSON' };
111
+ }
112
+ }
113
+
114
+ function checkAccessibility() {
115
+ if (process.platform !== 'darwin') {
116
+ return { name: 'macOS Accessibility', level: 'warn', detail: 'only checked on macOS' };
117
+ }
118
+
119
+ try {
120
+ execSync(
121
+ 'osascript -e \'tell application "System Events" to get name of first process\'',
122
+ { stdio: 'pipe' }
123
+ );
124
+ return { name: 'macOS Accessibility', level: 'pass', detail: 'System Events access available' };
125
+ } catch {
126
+ return {
127
+ name: 'macOS Accessibility',
128
+ level: 'warn',
129
+ detail: 'Grant Accessibility to Terminal and Cursor in System Settings.'
130
+ };
131
+ }
132
+ }
@@ -207,7 +207,7 @@ export async function initCommand(opts) {
207
207
  writeFileSync(join(dir, 'log.md'), `# ${project} — Agent Log\n\n## COMPRESSED CONTEXT\n\n(No compressed context yet.)\n\n## MESSAGE LOG\n\n(Agents append messages below this line.)\n`);
208
208
  writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
209
209
  const gitignorePath = join(dir, '.gitignore');
210
- const requiredIgnores = ['.env', '.agentxchain-trigger.json', '.agentxchain-prompts/'];
210
+ const requiredIgnores = ['.env', '.agentxchain-trigger.json', '.agentxchain-prompts/', '.agentxchain-workspaces/'];
211
211
  if (!existsSync(gitignorePath)) {
212
212
  writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
213
213
  } else {
@@ -245,6 +245,7 @@ export async function initCommand(opts) {
245
245
  const vsResult = generateVSCodeFiles(dir, config);
246
246
 
247
247
  const agentCount = Object.keys(agents).length;
248
+ const kickoffId = pickPmKickoffId(agents);
248
249
  console.log('');
249
250
  console.log(chalk.green(` ✓ Created ${chalk.bold(folderName)}/`));
250
251
  console.log('');
@@ -264,7 +265,18 @@ export async function initCommand(opts) {
264
265
  console.log('');
265
266
  console.log(` ${chalk.cyan('Next:')}`);
266
267
  console.log(` ${chalk.bold(`cd ${folderName}`)}`);
267
- console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# opens Cursor windows + copies agent prompts')}`);
268
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock agents start working')}`);
268
+ console.log(` ${chalk.bold(`agentxchain start --agent ${kickoffId}`)} ${chalk.dim('# PM-first kickoff: align scope with human')}`);
269
+ console.log(` ${chalk.bold('agentxchain start --remaining')} ${chalk.dim('# launch rest of team after PM planning')}`);
270
+ console.log(` ${chalk.bold('agentxchain supervise --autonudge')} ${chalk.dim('# watch + AppleScript nudge loop')}`);
271
+ console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock when you are ready')}`);
269
272
  console.log('');
270
273
  }
274
+
275
+ function pickPmKickoffId(agents) {
276
+ if (agents.pm) return 'pm';
277
+ for (const [id, def] of Object.entries(agents || {})) {
278
+ const name = String(def?.name || '').toLowerCase();
279
+ if (name.includes('product manager')) return id;
280
+ }
281
+ return Object.keys(agents || {})[0] || 'pm';
282
+ }
@@ -1,6 +1,5 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig } from '../lib/config.js';
3
- import { generateSeedPrompt } from '../lib/seed-prompt.js';
4
3
 
5
4
  export async function startCommand(opts) {
6
5
  const result = loadConfig();
@@ -26,15 +25,36 @@ export async function startCommand(opts) {
26
25
  process.exit(1);
27
26
  }
28
27
 
28
+ if (opts.agent && opts.remaining) {
29
+ console.log(chalk.red(' --agent and --remaining cannot be used together.'));
30
+ process.exit(1);
31
+ }
32
+
33
+ const launchConfig = buildLaunchConfig(config, opts);
34
+ const launchAgentIds = Object.keys(launchConfig.agents || {});
35
+ const launchCount = launchAgentIds.length;
36
+
37
+ if (launchCount === 0) {
38
+ console.log(chalk.red(' No agents selected to launch.'));
39
+ if (opts.remaining) {
40
+ console.log(chalk.dim(' Tip: this usually means only PM exists.'));
41
+ }
42
+ process.exit(1);
43
+ }
44
+
29
45
  console.log('');
30
46
  console.log(chalk.bold(` ${agentCount} agents configured for ${config.project}`));
47
+ if (opts.remaining) {
48
+ console.log(chalk.cyan(` Launch mode: remaining agents (${launchCount})`));
49
+ } else if (opts.agent) {
50
+ console.log(chalk.cyan(` Launch mode: single agent (${opts.agent})`));
51
+ }
31
52
  console.log('');
32
53
 
33
54
  if (opts.dryRun) {
34
55
  console.log(chalk.yellow(' DRY RUN — showing agents:'));
35
56
  console.log('');
36
- for (const [id, agent] of Object.entries(config.agents)) {
37
- if (opts.agent && opts.agent !== id) continue;
57
+ for (const [id, agent] of Object.entries(launchConfig.agents)) {
38
58
  console.log(` ${chalk.cyan(id)} — ${agent.name}`);
39
59
  console.log(chalk.dim(` ${agent.mandate.slice(0, 80)}...`));
40
60
  console.log('');
@@ -42,12 +62,21 @@ export async function startCommand(opts) {
42
62
  return;
43
63
  }
44
64
 
65
+ if (!opts.agent && !opts.remaining) {
66
+ const kickoffId = pickPmAgentId(config);
67
+ if (kickoffId) {
68
+ console.log(chalk.yellow(` Tip: for first run, start with ${chalk.bold(`agentxchain start --agent ${kickoffId}`)}.`));
69
+ console.log(chalk.dim(' Then use `agentxchain start --remaining` once PM planning is complete.'));
70
+ console.log('');
71
+ }
72
+ }
73
+
45
74
  switch (ide) {
46
75
  case 'vscode': {
47
76
  console.log(chalk.green(' Agents are set up as VS Code custom agents.'));
48
77
  console.log('');
49
78
  console.log(chalk.dim(' Your agents in .github/agents/:'));
50
- for (const [id, agent] of Object.entries(config.agents)) {
79
+ for (const [id, agent] of Object.entries(launchConfig.agents)) {
51
80
  console.log(` ${chalk.cyan(id)}.agent.md — ${agent.name}`);
52
81
  }
53
82
  console.log('');
@@ -65,12 +94,12 @@ export async function startCommand(opts) {
65
94
  }
66
95
  case 'cursor': {
67
96
  const { launchCursorLocal } = await import('../adapters/cursor-local.js');
68
- await launchCursorLocal(config, root, opts);
97
+ await launchCursorLocal(launchConfig, root, opts);
69
98
  break;
70
99
  }
71
100
  case 'claude-code': {
72
101
  const { launchClaudeCodeAgents } = await import('../adapters/claude-code.js');
73
- await launchClaudeCodeAgents(config, root, opts);
102
+ await launchClaudeCodeAgents(launchConfig, root, opts);
74
103
  break;
75
104
  }
76
105
  default:
@@ -78,3 +107,38 @@ export async function startCommand(opts) {
78
107
  process.exit(1);
79
108
  }
80
109
  }
110
+
111
+ function buildLaunchConfig(config, opts) {
112
+ if (opts.agent) {
113
+ return {
114
+ ...config,
115
+ agents: { [opts.agent]: config.agents[opts.agent] }
116
+ };
117
+ }
118
+
119
+ if (opts.remaining) {
120
+ const pmId = pickPmAgentId(config);
121
+ const agents = { ...config.agents };
122
+
123
+ if (pmId && agents[pmId]) {
124
+ delete agents[pmId];
125
+ } else {
126
+ const firstId = Object.keys(agents)[0];
127
+ if (firstId) delete agents[firstId];
128
+ }
129
+
130
+ return { ...config, agents };
131
+ }
132
+
133
+ return config;
134
+ }
135
+
136
+ function pickPmAgentId(config) {
137
+ if (config.agents.pm) return 'pm';
138
+
139
+ for (const [id, def] of Object.entries(config.agents || {})) {
140
+ const name = String(def?.name || '').toLowerCase();
141
+ if (name.includes('product manager')) return id;
142
+ }
143
+ return null;
144
+ }
@@ -0,0 +1,100 @@
1
+ import { existsSync } from 'fs';
2
+ import { spawn } from 'child_process';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import chalk from 'chalk';
6
+ import { loadConfig } from '../lib/config.js';
7
+
8
+ export async function superviseCommand(opts) {
9
+ const result = loadConfig();
10
+ if (!result) {
11
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
12
+ process.exit(1);
13
+ }
14
+
15
+ const { root } = result;
16
+
17
+ const currentDir = dirname(fileURLToPath(import.meta.url));
18
+ const cliBin = join(currentDir, '../../bin/agentxchain.js');
19
+ const runNudgeScript = join(currentDir, '../../scripts/run-autonudge.sh');
20
+ const interval = Number(opts.interval || 3);
21
+
22
+ if (opts.autonudge) {
23
+ if (process.platform !== 'darwin') {
24
+ console.log(chalk.red(' --autonudge currently supports macOS only.'));
25
+ process.exit(1);
26
+ }
27
+ if (!existsSync(runNudgeScript)) {
28
+ console.log(chalk.red(` Auto-nudge script not found: ${runNudgeScript}`));
29
+ process.exit(1);
30
+ }
31
+ }
32
+
33
+ console.log('');
34
+ console.log(chalk.bold(' AgentXchain Supervisor'));
35
+ console.log(chalk.dim(` Project: ${root}`));
36
+ console.log(chalk.dim(` Mode: ${opts.autonudge ? 'watch + auto-nudge' : 'watch only'}`));
37
+ if (opts.autonudge) {
38
+ console.log(chalk.dim(` Auto-send: ${opts.send ? 'enabled' : 'disabled (paste-only)'}`));
39
+ console.log(chalk.dim(` Nudge poll: ${interval}s`));
40
+ }
41
+ console.log('');
42
+
43
+ const children = [];
44
+
45
+ const watchChild = spawn(process.execPath, [cliBin, 'watch'], {
46
+ cwd: root,
47
+ stdio: 'inherit',
48
+ env: { ...process.env, AGENTXCHAIN_WATCH_DAEMON: '0' }
49
+ });
50
+ children.push(watchChild);
51
+
52
+ let nudgeChild = null;
53
+ if (opts.autonudge) {
54
+ const nudgeArgs = [runNudgeScript, '--project', root, '--interval', String(interval)];
55
+ nudgeArgs.push(opts.send ? '--send' : '--paste-only');
56
+ nudgeChild = spawn('bash', nudgeArgs, {
57
+ cwd: root,
58
+ stdio: 'inherit'
59
+ });
60
+ children.push(nudgeChild);
61
+ }
62
+
63
+ console.log(chalk.green(` ✓ Watch PID: ${watchChild.pid}`));
64
+ if (nudgeChild) console.log(chalk.green(` ✓ Auto-nudge PID: ${nudgeChild.pid}`));
65
+ console.log(chalk.dim(' Press Ctrl+C to stop supervisor and child processes.'));
66
+ console.log('');
67
+
68
+ let shuttingDown = false;
69
+ const shutdown = () => {
70
+ if (shuttingDown) return;
71
+ shuttingDown = true;
72
+ for (const child of children) {
73
+ if (!child.killed) {
74
+ try { child.kill('SIGTERM'); } catch {}
75
+ }
76
+ }
77
+ setTimeout(() => process.exit(0), 200);
78
+ };
79
+
80
+ process.on('SIGINT', shutdown);
81
+ process.on('SIGTERM', shutdown);
82
+
83
+ watchChild.on('exit', code => {
84
+ if (!shuttingDown) {
85
+ console.log('');
86
+ console.log(chalk.red(` Watch process exited unexpectedly (code ${code ?? 'unknown'}).`));
87
+ shutdown();
88
+ }
89
+ });
90
+
91
+ if (nudgeChild) {
92
+ nudgeChild.on('exit', code => {
93
+ if (!shuttingDown) {
94
+ console.log('');
95
+ console.log(chalk.red(` Auto-nudge exited unexpectedly (code ${code ?? 'unknown'}).`));
96
+ shutdown();
97
+ }
98
+ });
99
+ }
100
+ }