a2acalling 0.6.48 → 0.6.50
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/bin/cli.js +79 -2
- package/docs/plans/2026-02-16-auto-updater.md +1284 -0
- package/docs/plans/2026-02-16-e2e-test-prompt-sequence.md +3085 -0
- package/docs/plans/2026-02-17-claude-code-codex-skills.md +770 -0
- package/docs/prompts/e2e-test-agent.md +368 -0
- package/docs/protocol.md +121 -0
- package/package.json +1 -1
- package/scripts/install-skills.js +80 -0
- package/scripts/postinstall.js +12 -0
- package/src/dashboard/public/app.js +108 -1
- package/src/dashboard/public/index.html +9 -0
- package/src/dashboard/public/style.css +27 -0
- package/src/lib/claude-subagent.js +6 -5
- package/src/lib/config.js +42 -0
- package/src/lib/conversation-driver.js +74 -24
- package/src/lib/openclaw-integration.js +22 -66
- package/src/lib/runtime-adapter.js +8 -3
- package/src/lib/summary-formatter.js +168 -0
- package/src/lib/summary-prompt.js +203 -0
- package/src/lib/tokens.js +13 -1
- package/src/lib/turn-timeout.js +52 -0
- package/src/lib/update-checker.js +93 -0
- package/src/lib/update-manager.js +313 -0
- package/src/routes/a2a.js +9 -1
- package/src/routes/dashboard.js +103 -1
- package/src/server.js +130 -26
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# E2E Test Agent Prompt Sequence
|
|
2
|
+
|
|
3
|
+
Run a full end-to-end validation of `a2acalling` in an isolated environment. Execute each step sequentially. If a step fails, log the failure and continue to the next step unless the failure is blocking (steps 1-3 block all subsequent steps).
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Before starting, verify:
|
|
8
|
+
|
|
9
|
+
- **Node.js** >= 18.0.0 (`node --version`)
|
|
10
|
+
- **npm** available (`npm --version`)
|
|
11
|
+
- **Ports** 3100-3199 range available (used by test servers)
|
|
12
|
+
- **Disk** at least 200MB free in system temp directory
|
|
13
|
+
- **Network** localhost connections allowed (no firewall blocking loopback)
|
|
14
|
+
|
|
15
|
+
If any prerequisite is missing, report it as a step-0 failure and abort.
|
|
16
|
+
|
|
17
|
+
## Output Format
|
|
18
|
+
|
|
19
|
+
Report each step as a JSON object:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"step": 1,
|
|
24
|
+
"name": "install",
|
|
25
|
+
"status": "pass",
|
|
26
|
+
"duration_ms": 4230,
|
|
27
|
+
"error": null
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
On completion, return a JSON array of all step results:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"run_id": "e2e_<timestamp>",
|
|
36
|
+
"started_at": "<ISO-8601>",
|
|
37
|
+
"finished_at": "<ISO-8601>",
|
|
38
|
+
"total_steps": 9,
|
|
39
|
+
"passed": 8,
|
|
40
|
+
"failed": 1,
|
|
41
|
+
"results": [ ... ]
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Step 1: Install from npm
|
|
48
|
+
|
|
49
|
+
**What to do:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
WORKDIR=$(mktemp -d -t a2a-e2e-XXXXXX)
|
|
53
|
+
cd "$WORKDIR"
|
|
54
|
+
npm init -y --silent
|
|
55
|
+
npm install a2acalling
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Expected outcome:**
|
|
59
|
+
- Exit code 0 from `npm install`
|
|
60
|
+
- `node_modules/a2acalling/bin/cli.js` exists
|
|
61
|
+
- `node_modules/a2acalling/src/server.js` exists
|
|
62
|
+
|
|
63
|
+
**Failure:**
|
|
64
|
+
- If npm install fails, record the stderr and abort all remaining steps (blocking).
|
|
65
|
+
|
|
66
|
+
**Variables to carry forward:**
|
|
67
|
+
- `WORKDIR` -- root temp directory
|
|
68
|
+
- `CLI` = `$WORKDIR/node_modules/.bin/a2a`
|
|
69
|
+
- `A2A_CONFIG_DIR` = `$WORKDIR/config` (create this directory)
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Step 2: Run Quickstart Onboarding
|
|
74
|
+
|
|
75
|
+
**What to do:**
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
export A2A_CONFIG_DIR="$WORKDIR/config"
|
|
79
|
+
export CI=true
|
|
80
|
+
mkdir -p "$A2A_CONFIG_DIR"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
First, write a minimal config to simulate port detection completing:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cat > "$A2A_CONFIG_DIR/a2a-config.json" << 'CONF'
|
|
87
|
+
{
|
|
88
|
+
"onboarding": { "version": 2, "step": "awaiting_disclosure", "server_port": 3100 },
|
|
89
|
+
"agent": { "hostname": "localhost:3100", "name": "e2e-test-agent" },
|
|
90
|
+
"tiers": {}
|
|
91
|
+
}
|
|
92
|
+
CONF
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then submit the disclosure manifest:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node "$CLI" onboard --submit '{
|
|
99
|
+
"tiers": {
|
|
100
|
+
"public": {
|
|
101
|
+
"topics": [{"topic": "General", "description": "Open discussion"}],
|
|
102
|
+
"objectives": [],
|
|
103
|
+
"do_not_discuss": []
|
|
104
|
+
},
|
|
105
|
+
"friends": {
|
|
106
|
+
"topics": [{"topic": "Projects", "description": "Current work"}],
|
|
107
|
+
"objectives": [],
|
|
108
|
+
"do_not_discuss": []
|
|
109
|
+
},
|
|
110
|
+
"family": {
|
|
111
|
+
"topics": [{"topic": "Everything", "description": "Full access"}],
|
|
112
|
+
"objectives": [],
|
|
113
|
+
"do_not_discuss": []
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"never_disclose": ["passwords", "api-keys"],
|
|
117
|
+
"personality_notes": "E2E test agent. Direct and minimal responses."
|
|
118
|
+
}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Expected outcome:**
|
|
122
|
+
- Exit code 0
|
|
123
|
+
- stdout contains "Onboarding complete"
|
|
124
|
+
- `$A2A_CONFIG_DIR/a2a-config.json` has `onboarding.step` set to `"complete"`
|
|
125
|
+
|
|
126
|
+
**Failure:**
|
|
127
|
+
- If onboarding fails, record stdout/stderr and abort (blocking).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Step 3: Verify Server Health
|
|
132
|
+
|
|
133
|
+
**What to do:**
|
|
134
|
+
|
|
135
|
+
Start the server, then check health endpoints:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
node "$WORKDIR/node_modules/a2acalling/src/server.js" &
|
|
139
|
+
SERVER_PID=$!
|
|
140
|
+
sleep 2
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Check ping:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
curl -s http://localhost:3100/api/a2a/ping
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Check status:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
curl -s http://localhost:3100/api/a2a/status
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Expected outcome:**
|
|
156
|
+
- Ping returns `{"pong": true, "timestamp": "..."}` with HTTP 200
|
|
157
|
+
- Status returns JSON with `"a2a": true` and a `"version"` field
|
|
158
|
+
- Server process is running (PID exists)
|
|
159
|
+
|
|
160
|
+
**Failure:**
|
|
161
|
+
- If server does not start or endpoints do not respond within 10 seconds, abort (blocking).
|
|
162
|
+
|
|
163
|
+
**Variables to carry forward:**
|
|
164
|
+
- `SERVER_PID`
|
|
165
|
+
- `BASE_URL` = `http://localhost:3100`
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Step 4: Create Invite Token
|
|
170
|
+
|
|
171
|
+
**What to do:**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
node "$CLI" create --name "E2E-Caller" --tier public --expires 1h
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Parse the output to extract the token and invite URL.
|
|
178
|
+
|
|
179
|
+
**Expected outcome:**
|
|
180
|
+
- Exit code 0
|
|
181
|
+
- Output contains an invite URL matching `a2a://localhost:3100/fed_...`
|
|
182
|
+
- Output contains a token matching `fed_[A-Za-z0-9_-]+`
|
|
183
|
+
|
|
184
|
+
**Variables to carry forward:**
|
|
185
|
+
- `TOKEN` -- the `fed_...` string
|
|
186
|
+
- `INVITE_URL` -- the full `a2a://...` URL
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Step 5: Test Inbound Call
|
|
191
|
+
|
|
192
|
+
**What to do:**
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
196
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
197
|
+
-H "Content-Type: application/json" \
|
|
198
|
+
-d '{
|
|
199
|
+
"message": "Hello from E2E test",
|
|
200
|
+
"caller": {
|
|
201
|
+
"name": "E2E Test Runner",
|
|
202
|
+
"instance": "localhost",
|
|
203
|
+
"context": "Automated E2E validation"
|
|
204
|
+
}
|
|
205
|
+
}'
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Expected outcome:**
|
|
209
|
+
- HTTP 200
|
|
210
|
+
- Response JSON has `"success": true`
|
|
211
|
+
- Response JSON has a `"conversation_id"` starting with `conv_`
|
|
212
|
+
- Response JSON has a non-empty `"response"` field
|
|
213
|
+
- Response JSON has `"can_continue": true`
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Step 6: Test Multi-turn Conversation
|
|
218
|
+
|
|
219
|
+
**What to do:**
|
|
220
|
+
|
|
221
|
+
Use the `conversation_id` from step 5. Make two follow-up calls:
|
|
222
|
+
|
|
223
|
+
Call 1:
|
|
224
|
+
```bash
|
|
225
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
226
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
227
|
+
-H "Content-Type: application/json" \
|
|
228
|
+
-d "{
|
|
229
|
+
\"message\": \"Follow-up question 1\",
|
|
230
|
+
\"conversation_id\": \"$CONVERSATION_ID\"
|
|
231
|
+
}"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Call 2:
|
|
235
|
+
```bash
|
|
236
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
237
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
238
|
+
-H "Content-Type: application/json" \
|
|
239
|
+
-d "{
|
|
240
|
+
\"message\": \"Follow-up question 2\",
|
|
241
|
+
\"conversation_id\": \"$CONVERSATION_ID\"
|
|
242
|
+
}"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Expected outcome:**
|
|
246
|
+
- Both calls return HTTP 200 with `"success": true`
|
|
247
|
+
- Both return the same `conversation_id` as the original
|
|
248
|
+
- Both have non-empty `"response"` fields
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Step 7: Test Error Cases
|
|
253
|
+
|
|
254
|
+
Run three negative tests:
|
|
255
|
+
|
|
256
|
+
### 7a: No auth header
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/api/a2a/invoke" \
|
|
260
|
+
-H "Content-Type: application/json" \
|
|
261
|
+
-d '{"message": "no auth"}'
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Expected:** HTTP 401, response body has `"error": "missing_token"`
|
|
265
|
+
|
|
266
|
+
### 7b: Bad token
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
270
|
+
-H "Authorization: Bearer fed_invalid_token_value" \
|
|
271
|
+
-H "Content-Type: application/json" \
|
|
272
|
+
-d '{"message": "bad token"}'
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Expected:** HTTP 401 or 403, response body has `"success": false`
|
|
276
|
+
|
|
277
|
+
### 7c: Missing message body
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
281
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
282
|
+
-H "Content-Type: application/json" \
|
|
283
|
+
-d '{}'
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Expected:** HTTP 400, response body has `"error": "missing_message"`
|
|
287
|
+
|
|
288
|
+
**Step passes only if all three sub-tests pass.** Report sub-test details in the error field if any fail.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Step 8: Test Token Revocation
|
|
293
|
+
|
|
294
|
+
**What to do:**
|
|
295
|
+
|
|
296
|
+
First, find the token ID:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
node "$CLI" list
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Parse the token ID (`tok_...`) from the output, then revoke it:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
node "$CLI" revoke "$TOKEN_ID"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Then attempt a call with the revoked token:
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
curl -s -X POST "$BASE_URL/api/a2a/invoke" \
|
|
312
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
313
|
+
-H "Content-Type: application/json" \
|
|
314
|
+
-d '{"message": "should fail"}'
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Expected outcome:**
|
|
318
|
+
- Revoke command exits 0
|
|
319
|
+
- Post-revocation call returns `"success": false`
|
|
320
|
+
- Error field is `"token_revoked"` or similar auth error
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Step 9: Cleanup
|
|
325
|
+
|
|
326
|
+
**What to do:**
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
kill $SERVER_PID 2>/dev/null
|
|
330
|
+
wait $SERVER_PID 2>/dev/null
|
|
331
|
+
|
|
332
|
+
rm -rf "$WORKDIR"
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Verify:
|
|
336
|
+
```bash
|
|
337
|
+
! kill -0 $SERVER_PID 2>/dev/null # process is gone
|
|
338
|
+
[ ! -d "$WORKDIR" ] # directory is gone
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Expected outcome:**
|
|
342
|
+
- Server process is terminated
|
|
343
|
+
- Temp directory is removed
|
|
344
|
+
- No orphaned processes on port 3100
|
|
345
|
+
|
|
346
|
+
**This step always passes unless cleanup throws an unexpected error.**
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Failure Reporting
|
|
351
|
+
|
|
352
|
+
When a step fails, generate a Linear bug report payload:
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"title": "E2E: Step <N> (<name>) failed",
|
|
357
|
+
"description": "## Failure\n\n<error message>\n\n## Reproduction\n\nRun `node test/e2e/orchestrate.js --verbose`\n\n## Environment\n\n- Node: <version>\n- npm: <version>\n- OS: <platform>\n- a2acalling: <version>",
|
|
358
|
+
"priority": 2,
|
|
359
|
+
"labels": ["bug", "e2e"],
|
|
360
|
+
"team": "ENG"
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Priority mapping:
|
|
365
|
+
- Steps 1-3 (blocking): priority 1 (Urgent)
|
|
366
|
+
- Steps 4-6 (core flow): priority 2 (High)
|
|
367
|
+
- Steps 7-8 (error handling): priority 3 (Normal)
|
|
368
|
+
- Step 9 (cleanup): priority 4 (Low)
|
package/docs/protocol.md
CHANGED
|
@@ -309,6 +309,127 @@ a2a_call({
|
|
|
309
309
|
}
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
+
## E2E Testing
|
|
313
|
+
|
|
314
|
+
### Architecture
|
|
315
|
+
|
|
316
|
+
E2E tests run in fully isolated environments. Each test gets its own temp directory, config directory (`A2A_CONFIG_DIR`), and ephemeral port. No shared state between tests; no pollution of the host system.
|
|
317
|
+
|
|
318
|
+
Core components:
|
|
319
|
+
|
|
320
|
+
| Component | File | Purpose |
|
|
321
|
+
|-----------|------|---------|
|
|
322
|
+
| Environment | `test/e2e/env.js` | Creates isolated temp dir, config dir, port finder, cleanup |
|
|
323
|
+
| Two-server harness | `test/e2e/two-server.js` | Spins up two independent A2A servers for cross-agent tests |
|
|
324
|
+
| CLI runner | `test/e2e/cli-runner.js` | Wraps `bin/cli.js` as child process with structured output |
|
|
325
|
+
| Agent prompt | `docs/prompts/e2e-test-agent.md` | 9-step prompt sequence for Claude subagent validation |
|
|
326
|
+
|
|
327
|
+
### Test Categories
|
|
328
|
+
|
|
329
|
+
- **Infrastructure** (`env.test.js`) -- Isolated environments, port allocation, cleanup
|
|
330
|
+
- **CLI integration** (`cli-runner.test.js`) -- Command execution, onboarding, exit codes, timeouts
|
|
331
|
+
- **Cross-agent flow** (`two-server.test.js`) -- Two servers, token isolation, ping/invoke across instances
|
|
332
|
+
- **Error handling** -- Bad tokens, missing auth, revoked tokens, malformed requests
|
|
333
|
+
- **Summary validation** -- Prompt correctness across `server.js`, `openclaw-integration.js`, `claude-subagent.js` paths
|
|
334
|
+
|
|
335
|
+
### Entry Points
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
# Run all tests (unit + integration + E2E)
|
|
339
|
+
node test/run.js
|
|
340
|
+
|
|
341
|
+
# Run E2E tests only
|
|
342
|
+
node test/run.js --e2e
|
|
343
|
+
|
|
344
|
+
# Standalone orchestrator with verbose or JSON output
|
|
345
|
+
node test/e2e/orchestrate.js [--verbose] [--json]
|
|
346
|
+
|
|
347
|
+
# Default: excludes E2E for faster feedback
|
|
348
|
+
node test/run.js
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The `--e2e` flag filters to files under `test/e2e/`. The standalone orchestrator runs the full 9-step sequence from `docs/prompts/e2e-test-agent.md` and outputs structured JSON results.
|
|
352
|
+
|
|
353
|
+
### File Structure
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
test/e2e/
|
|
357
|
+
env.js # createE2EEnv() — isolated temp + config + port
|
|
358
|
+
env.test.js # Tests for env isolation and port allocation
|
|
359
|
+
cli-runner.js # CLIRunner class — child-process CLI wrapper
|
|
360
|
+
cli-runner.test.js # Tests for CLI execution, onboarding, timeouts
|
|
361
|
+
two-server.js # TwoServerHarness — two independent A2A instances
|
|
362
|
+
two-server.test.js # Tests for cross-agent token isolation and ping
|
|
363
|
+
orchestrate.js # Standalone E2E orchestrator (planned)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Adding New E2E Tests
|
|
367
|
+
|
|
368
|
+
1. Create `test/e2e/<name>.test.js` exporting `function(test, assert, helpers)`.
|
|
369
|
+
2. Use `createE2EEnv()` for isolation. Always call `env.cleanup()` in a finally block.
|
|
370
|
+
3. Use `TwoServerHarness` if you need two agents. Call `harness.teardown()` after.
|
|
371
|
+
4. Use `CLIRunner` for CLI interactions. It handles `A2A_CONFIG_DIR` automatically.
|
|
372
|
+
5. The test runner discovers all `*.test.js` files recursively -- no registration needed.
|
|
373
|
+
|
|
374
|
+
Example skeleton:
|
|
375
|
+
|
|
376
|
+
```js
|
|
377
|
+
module.exports = function (test, assert, helpers) {
|
|
378
|
+
const { createE2EEnv } = require('./env');
|
|
379
|
+
|
|
380
|
+
test('my new E2E scenario', async () => {
|
|
381
|
+
const env = createE2EEnv('my-test');
|
|
382
|
+
try {
|
|
383
|
+
// ... test logic using env.configDir, env.findAvailablePort()
|
|
384
|
+
} finally {
|
|
385
|
+
env.cleanup();
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## CLI Skills (Claude Code & Codex)
|
|
392
|
+
|
|
393
|
+
A2A ships with slash commands for Claude Code and agent instructions for Codex CLI.
|
|
394
|
+
|
|
395
|
+
### Installation
|
|
396
|
+
|
|
397
|
+
```bash
|
|
398
|
+
a2a skills # Install into current project
|
|
399
|
+
a2a skills --check # See what would be installed
|
|
400
|
+
a2a skills --force # Overwrite existing files
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Skills are also installed automatically on `npm install -g a2acalling`.
|
|
404
|
+
|
|
405
|
+
### Claude Code Commands
|
|
406
|
+
|
|
407
|
+
| Command | Description |
|
|
408
|
+
|---------|-------------|
|
|
409
|
+
| `/a2a-call <contact> <msg>` | Call another agent (multi-turn) |
|
|
410
|
+
| `/a2a-invite [name] [--tier]` | Create invite token |
|
|
411
|
+
| `/a2a-contacts [add\|show\|ping\|rm]` | Manage contacts |
|
|
412
|
+
| `/a2a-status` | Server and agent health dashboard |
|
|
413
|
+
| `/a2a-setup` | First-time setup and onboarding |
|
|
414
|
+
|
|
415
|
+
Files installed to: `.claude/commands/a2a-*.md`
|
|
416
|
+
|
|
417
|
+
### Codex CLI
|
|
418
|
+
|
|
419
|
+
A2A agent instructions are installed to `.codex/AGENTS.md`. Codex reads this file automatically to understand available A2A commands, permission tiers, and workflows.
|
|
420
|
+
|
|
421
|
+
### Manual Installation
|
|
422
|
+
|
|
423
|
+
If the automatic install didn't work, copy the files manually:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Claude Code commands
|
|
427
|
+
cp node_modules/a2acalling/.claude/commands/a2a-*.md .claude/commands/
|
|
428
|
+
|
|
429
|
+
# Codex instructions
|
|
430
|
+
cp node_modules/a2acalling/.codex/AGENTS.md .codex/AGENTS.md
|
|
431
|
+
```
|
|
432
|
+
|
|
312
433
|
## Future Protocol Extensions (v1+)
|
|
313
434
|
|
|
314
435
|
- **Capability advertisement**: Agents declare what they can help with
|
package/package.json
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2A Skill Installer
|
|
3
|
+
*
|
|
4
|
+
* Copies Claude Code commands and Codex AGENTS.md into a target project directory.
|
|
5
|
+
* Idempotent: skips files that already exist with identical content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const PACKAGE_ROOT = path.join(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
const SKILL_FILES = [
|
|
14
|
+
{ src: '.claude/commands/a2a-call.md', dest: '.claude/commands/a2a-call.md' },
|
|
15
|
+
{ src: '.claude/commands/a2a-invite.md', dest: '.claude/commands/a2a-invite.md' },
|
|
16
|
+
{ src: '.claude/commands/a2a-contacts.md', dest: '.claude/commands/a2a-contacts.md' },
|
|
17
|
+
{ src: '.claude/commands/a2a-status.md', dest: '.claude/commands/a2a-status.md' },
|
|
18
|
+
{ src: '.claude/commands/a2a-setup.md', dest: '.claude/commands/a2a-setup.md' },
|
|
19
|
+
{ src: '.codex/AGENTS.md', dest: '.codex/AGENTS.md' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function installSkills(targetDir, options = {}) {
|
|
23
|
+
const result = { installed: [], skipped: [], errors: [] };
|
|
24
|
+
|
|
25
|
+
for (const file of SKILL_FILES) {
|
|
26
|
+
const srcPath = path.join(PACKAGE_ROOT, file.src);
|
|
27
|
+
const destPath = path.join(targetDir, file.dest);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
if (!fs.existsSync(srcPath)) {
|
|
31
|
+
result.errors.push({ file: file.src, error: 'Source file not found' });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const srcContent = fs.readFileSync(srcPath, 'utf8');
|
|
36
|
+
|
|
37
|
+
// Check if identical file already exists
|
|
38
|
+
if (!options.force && fs.existsSync(destPath)) {
|
|
39
|
+
const existing = fs.readFileSync(destPath, 'utf8');
|
|
40
|
+
if (existing === srcContent) {
|
|
41
|
+
result.skipped.push(file.dest);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create directory and write file
|
|
47
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(destPath, srcContent);
|
|
49
|
+
result.installed.push(file.dest);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
result.errors.push({ file: file.dest, error: err.message });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// CLI mode: node scripts/install-skills.js [targetDir] [--force]
|
|
59
|
+
if (require.main === module) {
|
|
60
|
+
const args = process.argv.slice(2);
|
|
61
|
+
const force = args.includes('--force');
|
|
62
|
+
const targetDir = args.find(a => !a.startsWith('-')) || process.cwd();
|
|
63
|
+
|
|
64
|
+
const result = installSkills(targetDir, { force });
|
|
65
|
+
|
|
66
|
+
if (result.installed.length) {
|
|
67
|
+
console.log(`Installed ${result.installed.length} A2A skill file(s):`);
|
|
68
|
+
result.installed.forEach(f => console.log(` + ${f}`));
|
|
69
|
+
}
|
|
70
|
+
if (result.skipped.length) {
|
|
71
|
+
console.log(`Skipped ${result.skipped.length} unchanged file(s)`);
|
|
72
|
+
}
|
|
73
|
+
if (result.errors.length) {
|
|
74
|
+
console.error(`Errors: ${result.errors.length}`);
|
|
75
|
+
result.errors.forEach(e => console.error(` ! ${e.file}: ${e.error}`));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { installSkills, SKILL_FILES };
|
package/scripts/postinstall.js
CHANGED
|
@@ -43,13 +43,25 @@ const result = spawnSync(process.execPath, [cliPath, 'quickstart'], {
|
|
|
43
43
|
|
|
44
44
|
if (result.error) {
|
|
45
45
|
// Don't fail the install — the agent will get onboarding when it runs `a2a`.
|
|
46
|
+
installSkillFiles();
|
|
46
47
|
installMacOSApp();
|
|
47
48
|
process.exit(0);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
installSkillFiles();
|
|
50
52
|
installMacOSApp();
|
|
51
53
|
process.exit(result.status || 0);
|
|
52
54
|
|
|
55
|
+
// Best-effort: install Claude Code + Codex skills into the workspace
|
|
56
|
+
function installSkillFiles() {
|
|
57
|
+
try {
|
|
58
|
+
const { installSkills } = require('./install-skills');
|
|
59
|
+
installSkills(initCwd);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// Silent — skills can be installed later with `a2a skills`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
53
65
|
// Download and install the native macOS app from GitHub Releases
|
|
54
66
|
function installMacOSApp() {
|
|
55
67
|
const os = require('os');
|