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 +101 -21
- package/bin/agentxchain.js +16 -0
- package/package.json +2 -1
- package/scripts/agentxchain-autonudge.applescript +107 -0
- package/scripts/build-binary.sh +42 -0
- package/scripts/publish-npm.sh +95 -0
- package/scripts/run-autonudge.sh +111 -0
- package/scripts/stop-autonudge.sh +13 -0
- package/src/adapters/cursor-local.js +109 -23
- package/src/commands/doctor.js +132 -0
- package/src/commands/init.js +15 -3
- package/src/commands/start.js +70 -6
- package/src/commands/supervise.js +100 -0
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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,
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- **
|
|
63
|
-
- **
|
|
64
|
-
- **
|
|
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
|
|
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
|
package/bin/agentxchain.js
CHANGED
|
@@ -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.
|
|
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(`
|
|
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
|
-
|
|
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 =
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
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(`
|
|
29
|
-
console.log(chalk.dim(`
|
|
58
|
+
console.log(chalk.green(` ✓ Prompt copied to clipboard.`));
|
|
59
|
+
console.log(chalk.dim(` Saved to: .agentxchain-prompts/${id}.prompt.md`));
|
|
30
60
|
|
|
31
|
-
|
|
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:
|
|
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(`
|
|
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.
|
|
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.
|
|
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('
|
|
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
|
|
140
|
+
function openCursorWindow(folderPath) {
|
|
86
141
|
try {
|
|
87
142
|
if (process.platform === 'darwin') {
|
|
88
|
-
execSync(`open -na "Cursor" --args "${
|
|
143
|
+
execSync(`open -na "Cursor" --args "${folderPath}"`, { stdio: 'ignore' });
|
|
89
144
|
return;
|
|
90
145
|
}
|
|
91
|
-
execSync(`cursor "${
|
|
92
|
-
} catch {
|
|
93
|
-
|
|
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
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -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(
|
|
268
|
-
console.log(` ${chalk.bold('agentxchain
|
|
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
|
+
}
|
package/src/commands/start.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
+
}
|