agentxchain 0.8.2 → 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
@@ -20,18 +20,21 @@ npx agentxchain init
20
20
  # 1. Create a project (interactive template selection)
21
21
  agentxchain init
22
22
 
23
- # 2. Launch agents opens a separate Cursor window per agent
23
+ # 2. PM-first kickoff (human + PM align scope first)
24
24
  cd my-project/
25
- agentxchain start
25
+ agentxchain start --agent pm
26
26
 
27
- # 3. For each window: paste the prompt (auto-copied to clipboard), select Agent mode, send
28
- # The CLI walks you through one agent at a time.
27
+ # 3. Launch remaining agents after planning is clear
28
+ agentxchain start --remaining
29
29
 
30
- # 4. Release the human lock — agents start claiming turns
30
+ # 4. Start supervisor (watch + auto-nudge)
31
+ agentxchain supervise --autonudge
32
+
33
+ # 5. Release the human lock — agents start claiming turns
31
34
  agentxchain release
32
35
  ```
33
36
 
34
- 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. No external referee 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.
35
38
 
36
39
  ## Commands
37
40
 
@@ -39,8 +42,10 @@ Each agent runs in its own Cursor window with a self-polling loop. Agents check
39
42
  |---------|-------------|
40
43
  | `init` | Create project folder with agents, protocol files, and templates |
41
44
  | `start` | Open a Cursor window per agent + copy prompts to clipboard |
45
+ | `supervise` | Run watcher and optional AppleScript auto-nudge together |
42
46
  | `generate` | Regenerate agent files from `agentxchain.json` |
43
47
  | `status` | Show lock holder, phase, turn number, agents |
48
+ | `doctor` | Validate local setup (tools, trigger flow, accessibility checks) |
44
49
  | `claim` | Human takes control (agents stop claiming) |
45
50
  | `release` | Hand lock back to agents |
46
51
  | `stop` | Terminate running Claude Code agent sessions |
@@ -60,20 +65,62 @@ agentxchain start --ide claude-code # Claude Code — spawns CLI processes
60
65
 
61
66
  ```bash
62
67
  agentxchain start --agent pm # launch only one specific agent
68
+ agentxchain start --remaining # launch all agents except PM (PM-first flow)
63
69
  agentxchain start --dry-run # preview agents without launching
64
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
65
73
  agentxchain release --force # force-release non-human holder lock
66
74
  ```
67
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
+
68
114
  ## How it works
69
115
 
70
116
  ### Cursor mode (default)
71
117
 
72
118
  1. `agentxchain start` opens a **separate Cursor window** for each agent
73
119
  2. Each window gets a unique prompt copied to clipboard
74
- 3. Agent prompts include a self-polling loop: read `lock.json` check if it's my turn → claim → work → release → sleep 60s → repeat
75
- 4. Agents know their rotation order from `agentxchain.json` and only claim when the previous agent released
76
- 5. Human can `claim` to pause and `release` to resume anytime
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
77
124
 
78
125
  ### VS Code mode
79
126
 
@@ -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.2",
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."
@@ -9,8 +9,13 @@ 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
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');
@@ -18,7 +23,9 @@ export async function launchCursorLocal(config, root, opts) {
18
23
 
19
24
  // Save all prompts first
20
25
  for (const [id, agent] of agentEntries) {
21
- const prompt = generatePollingPrompt(id, agent, config);
26
+ const prompt = isPmKickoff
27
+ ? generateKickoffPrompt(id, agent, config)
28
+ : generatePollingPrompt(id, agent, config);
22
29
  writeFileSync(join(promptDir, `${id}.prompt.md`), prompt);
23
30
  }
24
31
 
@@ -28,7 +35,9 @@ export async function launchCursorLocal(config, root, opts) {
28
35
 
29
36
  for (let i = 0; i < agentEntries.length; i++) {
30
37
  const [id, agent] = agentEntries[i];
31
- const prompt = generatePollingPrompt(id, agent, config);
38
+ const prompt = isPmKickoff
39
+ ? generateKickoffPrompt(id, agent, config)
40
+ : generatePollingPrompt(id, agent, config);
32
41
 
33
42
  // Create symlink: .agentxchain-workspaces/<id> -> project root
34
43
  const agentWorkspace = join(workspacesDir, id);
@@ -57,8 +66,13 @@ export async function launchCursorLocal(config, root, opts) {
57
66
  console.log(` ${chalk.bold('In the new Cursor window:')}`);
58
67
  console.log(` 1. Open chat (${chalk.bold('Cmd+L')})`);
59
68
  console.log(` 2. Paste the prompt (${chalk.bold('Cmd+V')})`);
60
- console.log(` 3. ${chalk.bold('Select Agent mode')} (not Ask/Edit)`);
61
- console.log(` 4. Send it (${chalk.bold('Enter')})`);
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
+ }
62
76
 
63
77
  if (i < agentEntries.length - 1) {
64
78
  console.log('');
@@ -78,12 +92,20 @@ export async function launchCursorLocal(config, root, opts) {
78
92
  console.log(chalk.green(` ✓ All ${total} agents launched in separate Cursor windows.`));
79
93
  console.log('');
80
94
  console.log(` ${chalk.cyan('Now run:')}`);
81
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock — agents start claiming turns')}`);
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
+ }
82
103
  console.log('');
83
104
  console.log(` ${chalk.dim('Other commands:')}`);
84
105
  console.log(` ${chalk.bold('agentxchain status')} ${chalk.dim('# check who holds the lock')}`);
85
106
  console.log(` ${chalk.bold('agentxchain claim')} ${chalk.dim('# pause agents and take control')}`);
86
- console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# optional: TTL safety net')}`);
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')}`);
87
109
  console.log('');
88
110
  console.log(chalk.dim(' Agents self-coordinate via lock.json polling (sleep 60s between checks).'));
89
111
  console.log(chalk.dim(' Re-paste a prompt: cat .agentxchain-prompts/<agent>.prompt.md | pbcopy'));
@@ -124,3 +146,36 @@ function openCursorWindow(folderPath) {
124
146
  execSync(`cursor --new-window "${folderPath}"`, { stdio: 'ignore' });
125
147
  } catch {}
126
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.`;
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
+ }
@@ -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
+ }