@blockrun/clawrouter 0.12.61 → 0.12.63

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.
Files changed (33) hide show
  1. package/docs/anthropic-cost-savings.md +349 -0
  2. package/docs/architecture.md +559 -0
  3. package/docs/assets/blockrun-248-day-cost-overrun-problem.png +0 -0
  4. package/docs/assets/blockrun-clawrouter-7-layer-token-compression-openclaw.png +0 -0
  5. package/docs/assets/blockrun-clawrouter-observation-compression-97-percent-token-savings.png +0 -0
  6. package/docs/assets/blockrun-clawrouter-openclaw-agentic-proxy-architecture.png +0 -0
  7. package/docs/assets/blockrun-clawrouter-openclaw-automatic-tier-routing-model-selection.png +0 -0
  8. package/docs/assets/blockrun-clawrouter-openclaw-error-classification-retry-storm-prevention.png +0 -0
  9. package/docs/assets/blockrun-clawrouter-openclaw-session-memory-journaling-vs-context-compounding.png +0 -0
  10. package/docs/assets/blockrun-clawrouter-vs-openclaw-standalone-comparison-production-safety.png +0 -0
  11. package/docs/assets/blockrun-clawrouter-x402-usdc-micropayment-wallet-budget-control.png +0 -0
  12. package/docs/assets/blockrun-openclaw-inference-layer-blind-spots.png +0 -0
  13. package/docs/blog-benchmark-2026-03.md +184 -0
  14. package/docs/blog-openclaw-cost-overruns.md +197 -0
  15. package/docs/clawrouter-savings.png +0 -0
  16. package/docs/configuration.md +512 -0
  17. package/docs/features.md +257 -0
  18. package/docs/image-generation.md +380 -0
  19. package/docs/plans/2026-02-03-smart-routing-design.md +267 -0
  20. package/docs/plans/2026-02-13-e2e-docker-deployment.md +1260 -0
  21. package/docs/plans/2026-02-28-worker-network.md +947 -0
  22. package/docs/plans/2026-03-18-error-classification.md +574 -0
  23. package/docs/plans/2026-03-19-exclude-models.md +538 -0
  24. package/docs/routing-profiles.md +81 -0
  25. package/docs/subscription-failover.md +320 -0
  26. package/docs/technical-routing-2026-03.md +322 -0
  27. package/docs/troubleshooting.md +159 -0
  28. package/docs/vision.md +49 -0
  29. package/docs/vs-openrouter.md +157 -0
  30. package/docs/worker-network.md +1241 -0
  31. package/package.json +3 -2
  32. package/scripts/reinstall.sh +8 -4
  33. package/scripts/update.sh +8 -4
@@ -0,0 +1,1260 @@
1
+ # ClawRouter E2E Testing, Docker Validation & Deployment
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Comprehensive E2E test coverage, Docker install/uninstall validation (10 cases), and automated deployment pipeline for ClawRouter.
6
+
7
+ **Architecture:** Three-phase approach: (1) Expand E2E tests to cover error scenarios, edge cases, and the recent fixes (504 timeout, settlement retry, large payload handling), (2) Build Docker-based installation testing covering npm global, OpenClaw plugin, upgrade/downgrade, and cleanup scenarios, (3) Automate deployment with pre-publish validation and version management.
8
+
9
+ **Tech Stack:** TypeScript, tsx test runner, Docker, npm, OpenClaw CLI, bash scripting
10
+
11
+ ---
12
+
13
+ ## Task 1: E2E Test Expansion
14
+
15
+ **Goal:** Add 10+ new E2E test cases covering error handling, edge cases, and recent bug fixes.
16
+
17
+ **Files:**
18
+
19
+ - Modify: `test/test-e2e.ts`
20
+ - Run: `npx tsx test/test-e2e.ts`
21
+
22
+ ### Step 1: Add test for 413 Payload Too Large (150KB limit)
23
+
24
+ **Code to add after existing tests (before cleanup section):**
25
+
26
+ ```typescript
27
+ // Test 8: 413 Payload Too Large — message array exceeds 150KB
28
+ allPassed =
29
+ (await test(
30
+ "413 error for oversized request (>150KB)",
31
+ async (p) => {
32
+ const largeMessage = "x".repeat(160 * 1024); // 160KB
33
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({
37
+ model: "deepseek/deepseek-chat",
38
+ messages: [{ role: "user", content: largeMessage }],
39
+ max_tokens: 10,
40
+ }),
41
+ });
42
+ if (res.status !== 413) {
43
+ const text = await res.text();
44
+ throw new Error(`Expected 413, got ${res.status}: ${text.slice(0, 200)}`);
45
+ }
46
+ const body = await res.json();
47
+ if (!body.error?.message?.includes("exceeds maximum"))
48
+ throw new Error("Missing size limit error message");
49
+ console.log(`(payload=${Math.round(largeMessage.length / 1024)}KB, status=413) `);
50
+ },
51
+ proxy,
52
+ )) && allPassed;
53
+ ```
54
+
55
+ ### Step 2: Add test for 400 Bad Request (malformed JSON)
56
+
57
+ ```typescript
58
+ // Test 9: 400 Bad Request — malformed JSON
59
+ allPassed =
60
+ (await test(
61
+ "400 error for malformed JSON",
62
+ async (p) => {
63
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
64
+ method: "POST",
65
+ headers: { "Content-Type": "application/json" },
66
+ body: "{invalid json}",
67
+ });
68
+ if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
69
+ const body = await res.json();
70
+ if (!body.error) throw new Error("Missing error object");
71
+ },
72
+ proxy,
73
+ )) && allPassed;
74
+ ```
75
+
76
+ ### Step 3: Add test for 400 Bad Request (missing required fields)
77
+
78
+ ```typescript
79
+ // Test 10: 400 Bad Request — missing messages field
80
+ allPassed =
81
+ (await test(
82
+ "400 error for missing messages field",
83
+ async (p) => {
84
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
85
+ method: "POST",
86
+ headers: { "Content-Type": "application/json" },
87
+ body: JSON.stringify({
88
+ model: "deepseek/deepseek-chat",
89
+ max_tokens: 10,
90
+ // missing messages
91
+ }),
92
+ });
93
+ if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
94
+ const body = await res.json();
95
+ if (!body.error?.message?.includes("messages"))
96
+ throw new Error("Error should mention missing messages");
97
+ },
98
+ proxy,
99
+ )) && allPassed;
100
+ ```
101
+
102
+ ### Step 4: Add test for large message array (200 messages limit)
103
+
104
+ ```typescript
105
+ // Test 11: 400 error for too many messages (>200)
106
+ allPassed =
107
+ (await test(
108
+ "400 error for message array exceeding 200 items",
109
+ async (p) => {
110
+ const messages = Array(201)
111
+ .fill(null)
112
+ .map((_, i) => ({ role: i % 2 === 0 ? "user" : "assistant", content: "test" }));
113
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
114
+ method: "POST",
115
+ headers: { "Content-Type": "application/json" },
116
+ body: JSON.stringify({
117
+ model: "deepseek/deepseek-chat",
118
+ messages,
119
+ max_tokens: 10,
120
+ }),
121
+ });
122
+ if (res.status !== 400) {
123
+ const text = await res.text();
124
+ throw new Error(`Expected 400, got ${res.status}: ${text.slice(0, 200)}`);
125
+ }
126
+ const body = await res.json();
127
+ if (!body.error?.message?.includes("200"))
128
+ throw new Error("Error should mention message limit");
129
+ console.log(`(messages=${messages.length}, status=400) `);
130
+ },
131
+ proxy,
132
+ )) && allPassed;
133
+ ```
134
+
135
+ ### Step 5: Add test for invalid model name
136
+
137
+ ```typescript
138
+ // Test 12: Model fallback — invalid model should fail gracefully
139
+ allPassed =
140
+ (await test(
141
+ "Invalid model returns clear error",
142
+ async (p) => {
143
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ body: JSON.stringify({
147
+ model: "invalid/nonexistent-model",
148
+ messages: [{ role: "user", content: "test" }],
149
+ max_tokens: 10,
150
+ }),
151
+ });
152
+ if (res.status !== 400) {
153
+ const text = await res.text();
154
+ throw new Error(`Expected 400, got ${res.status}: ${text.slice(0, 200)}`);
155
+ }
156
+ const body = await res.json();
157
+ if (!body.error) throw new Error("Missing error object");
158
+ },
159
+ proxy,
160
+ )) && allPassed;
161
+ ```
162
+
163
+ ### Step 6: Add test for concurrent requests (stress test)
164
+
165
+ ```typescript
166
+ // Test 13: Concurrent requests — send 5 parallel requests
167
+ allPassed =
168
+ (await test(
169
+ "Concurrent requests (5 parallel)",
170
+ async (p) => {
171
+ const makeRequest = () =>
172
+ fetch(`${p.baseUrl}/v1/chat/completions`, {
173
+ method: "POST",
174
+ headers: { "Content-Type": "application/json" },
175
+ body: JSON.stringify({
176
+ model: "deepseek/deepseek-chat",
177
+ messages: [{ role: "user", content: `Test ${Math.random()}` }],
178
+ max_tokens: 5,
179
+ }),
180
+ });
181
+
182
+ const start = Date.now();
183
+ const results = await Promise.all([
184
+ makeRequest(),
185
+ makeRequest(),
186
+ makeRequest(),
187
+ makeRequest(),
188
+ makeRequest(),
189
+ ]);
190
+ const elapsed = Date.now() - start;
191
+
192
+ const allSucceeded = results.every((r) => r.status === 200);
193
+ if (!allSucceeded) {
194
+ const statuses = results.map((r) => r.status).join(", ");
195
+ throw new Error(`Not all requests succeeded: ${statuses}`);
196
+ }
197
+
198
+ console.log(`(5 requests in ${elapsed}ms, avg=${Math.round(elapsed / 5)}ms) `);
199
+ },
200
+ proxy,
201
+ )) && allPassed;
202
+ ```
203
+
204
+ ### Step 7: Add test for negative max_tokens (should be rejected)
205
+
206
+ ```typescript
207
+ // Test 14: Negative max_tokens should be rejected
208
+ allPassed =
209
+ (await test(
210
+ "400 error for negative max_tokens",
211
+ async (p) => {
212
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
213
+ method: "POST",
214
+ headers: { "Content-Type": "application/json" },
215
+ body: JSON.stringify({
216
+ model: "deepseek/deepseek-chat",
217
+ messages: [{ role: "user", content: "test" }],
218
+ max_tokens: -100,
219
+ }),
220
+ });
221
+ if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
222
+ const body = await res.json();
223
+ if (!body.error) throw new Error("Missing error object");
224
+ },
225
+ proxy,
226
+ )) && allPassed;
227
+ ```
228
+
229
+ ### Step 8: Add test for empty messages array
230
+
231
+ ```typescript
232
+ // Test 15: Empty messages array should be rejected
233
+ allPassed =
234
+ (await test(
235
+ "400 error for empty messages array",
236
+ async (p) => {
237
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
238
+ method: "POST",
239
+ headers: { "Content-Type": "application/json" },
240
+ body: JSON.stringify({
241
+ model: "deepseek/deepseek-chat",
242
+ messages: [],
243
+ max_tokens: 10,
244
+ }),
245
+ });
246
+ if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
247
+ },
248
+ proxy,
249
+ )) && allPassed;
250
+ ```
251
+
252
+ ### Step 9: Add test for streaming with large response (token counting)
253
+
254
+ ```typescript
255
+ // Test 16: Streaming large response — verify token counting
256
+ allPassed =
257
+ (await test(
258
+ "Streaming with large output (token counting)",
259
+ async (p) => {
260
+ const res = await fetch(`${p.baseUrl}/v1/chat/completions`, {
261
+ method: "POST",
262
+ headers: { "Content-Type": "application/json" },
263
+ body: JSON.stringify({
264
+ model: "deepseek/deepseek-chat",
265
+ messages: [
266
+ {
267
+ role: "user",
268
+ content: "Write a 50-word story about a robot. Be concise.",
269
+ },
270
+ ],
271
+ max_tokens: 100,
272
+ stream: true,
273
+ }),
274
+ });
275
+ if (res.status !== 200) throw new Error(`Expected 200, got ${res.status}`);
276
+
277
+ const text = await res.text();
278
+ const lines = text.split("\n").filter((l) => l.startsWith("data: "));
279
+ const hasDone = lines.some((l) => l === "data: [DONE]");
280
+ if (!hasDone) throw new Error("Missing [DONE] marker");
281
+
282
+ let fullContent = "";
283
+ for (const line of lines.filter((l) => l !== "data: [DONE]")) {
284
+ try {
285
+ const parsed = JSON.parse(line.slice(6));
286
+ const delta = parsed.choices?.[0]?.delta?.content;
287
+ if (delta) fullContent += delta;
288
+ } catch {
289
+ // skip
290
+ }
291
+ }
292
+
293
+ const wordCount = fullContent.trim().split(/\s+/).length;
294
+ console.log(`(words=${wordCount}, chunks=${lines.length - 1}) `);
295
+ if (wordCount < 10) throw new Error(`Response too short: ${wordCount} words`);
296
+ },
297
+ proxy,
298
+ )) && allPassed;
299
+ ```
300
+
301
+ ### Step 10: Add test for balance check (verify wallet has funds)
302
+
303
+ ```typescript
304
+ // Test 17: Balance check before test (ensure wallet is funded)
305
+ allPassed =
306
+ (await test(
307
+ "Wallet has sufficient balance",
308
+ async (p) => {
309
+ if (!p.balanceMonitor) throw new Error("Balance monitor not available");
310
+ const balance = await p.balanceMonitor.checkBalance();
311
+ if (balance.isEmpty) throw new Error("Wallet is empty - please fund it");
312
+ console.log(`(balance=$${balance.balanceUSD.toFixed(2)}) `);
313
+ },
314
+ proxy,
315
+ )) && allPassed;
316
+ ```
317
+
318
+ ### Step 11: Run expanded E2E tests
319
+
320
+ Run:
321
+
322
+ ```bash
323
+ BLOCKRUN_WALLET_KEY=0x... npx tsx test/test-e2e.ts
324
+ ```
325
+
326
+ Expected output:
327
+
328
+ ```
329
+ === ClawRouter e2e tests ===
330
+
331
+ Starting proxy...
332
+ Proxy ready on port 8405
333
+ Health check ... (wallet: 0xABC...) PASS
334
+ Non-streaming request (deepseek/deepseek-chat) ... (response: "4") PASS
335
+ Streaming request (google/gemini-2.5-flash) ... (heartbeat=true, done=true, content="Hello") PASS
336
+ Smart routing: simple query (blockrun/auto → should pick cheap model) ... PASS
337
+ Smart routing: streaming (blockrun/auto, stream=true) ... PASS
338
+ Dedup: identical request returns cached response ... PASS
339
+ 404 for unknown path ... PASS
340
+ 413 error for oversized request (>150KB) ... (payload=160KB, status=413) PASS
341
+ 400 error for malformed JSON ... PASS
342
+ 400 error for missing messages field ... PASS
343
+ 400 error for message array exceeding 200 items ... (messages=201, status=400) PASS
344
+ Invalid model returns clear error ... PASS
345
+ Concurrent requests (5 parallel) ... (5 requests in 2500ms, avg=500ms) PASS
346
+ 400 error for negative max_tokens ... PASS
347
+ 400 error for empty messages array ... PASS
348
+ Streaming with large output (token counting) ... (words=52, chunks=15) PASS
349
+ Wallet has sufficient balance ... (balance=$5.23) PASS
350
+
351
+ === ALL TESTS PASSED ===
352
+ ```
353
+
354
+ ### Step 12: Commit E2E test expansion
355
+
356
+ ```bash
357
+ git add test/test-e2e.ts
358
+ git commit -m "test: expand E2E coverage with 10 new test cases
359
+
360
+ - Add 413 Payload Too Large test (150KB limit)
361
+ - Add 400 Bad Request tests (malformed JSON, missing fields)
362
+ - Add message array limit test (200 messages)
363
+ - Add invalid model error handling test
364
+ - Add concurrent request stress test (5 parallel)
365
+ - Add negative max_tokens validation test
366
+ - Add empty messages array validation test
367
+ - Add streaming large response test with token counting
368
+ - Add wallet balance check test
369
+
370
+ Covers recent bug fixes: 504 timeout prevention, settlement retry,
371
+ large payload truncation."
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Task 2: Docker Install/Uninstall Tests (10 Cases)
377
+
378
+ **Goal:** Validate ClawRouter installation, upgrade, uninstall across different methods and environments.
379
+
380
+ **Files:**
381
+
382
+ - Create: `test/docker-install-tests.sh`
383
+ - Create: `test/Dockerfile.install-test`
384
+ - Modify: `test/run-docker-test.sh`
385
+
386
+ ### Step 1: Create Dockerfile for installation testing
387
+
388
+ **Create `test/Dockerfile.install-test`:**
389
+
390
+ ```dockerfile
391
+ FROM node:22-slim
392
+
393
+ # Install system dependencies
394
+ RUN apt-get update && apt-get install -y \
395
+ git \
396
+ curl \
397
+ jq \
398
+ && rm -rf /var/lib/apt/lists/*
399
+
400
+ # Create test user
401
+ RUN useradd -m -s /bin/bash testuser
402
+
403
+ # Set up environment
404
+ USER testuser
405
+ WORKDIR /home/testuser
406
+
407
+ # Initialize npm config
408
+ RUN npm config set prefix ~/.npm-global
409
+ ENV PATH="/home/testuser/.npm-global/bin:$PATH"
410
+
411
+ CMD ["/bin/bash"]
412
+ ```
413
+
414
+ ### Step 2: Create bash test script with 10 test cases
415
+
416
+ **Create `test/docker-install-tests.sh`:**
417
+
418
+ ```bash
419
+ #!/bin/bash
420
+ set -e
421
+
422
+ PASS=0
423
+ FAIL=0
424
+
425
+ # Colors
426
+ GREEN='\033[0;32m'
427
+ RED='\033[0;31m'
428
+ NC='\033[0m' # No Color
429
+
430
+ # Test runner
431
+ test_case() {
432
+ local name=$1
433
+ local fn=$2
434
+
435
+ echo ""
436
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
437
+ echo "Test: $name"
438
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
439
+
440
+ if $fn; then
441
+ echo -e "${GREEN}✓ PASS${NC}"
442
+ ((PASS++))
443
+ else
444
+ echo -e "${RED}✗ FAIL${NC}"
445
+ ((FAIL++))
446
+ fi
447
+ }
448
+
449
+ # Test 1: Fresh npm global installation
450
+ test_fresh_install() {
451
+ echo "Installing @blockrun/clawrouter globally..."
452
+ npm install -g @blockrun/clawrouter@latest
453
+
454
+ echo "Verifying clawrouter command exists..."
455
+ which clawrouter || return 1
456
+
457
+ echo "Checking version..."
458
+ clawrouter --version || return 1
459
+
460
+ echo "Verifying package is in npm global list..."
461
+ npm list -g @blockrun/clawrouter || return 1
462
+
463
+ return 0
464
+ }
465
+
466
+ # Test 2: Uninstall verification
467
+ test_uninstall() {
468
+ echo "Uninstalling @blockrun/clawrouter..."
469
+ npm uninstall -g @blockrun/clawrouter
470
+
471
+ echo "Verifying clawrouter command is gone..."
472
+ if which clawrouter 2>/dev/null; then
473
+ echo "ERROR: clawrouter command still exists after uninstall"
474
+ return 1
475
+ fi
476
+
477
+ echo "Verifying package is not in npm global list..."
478
+ if npm list -g @blockrun/clawrouter 2>/dev/null; then
479
+ echo "ERROR: package still in npm list after uninstall"
480
+ return 1
481
+ fi
482
+
483
+ return 0
484
+ }
485
+
486
+ # Test 3: Reinstall after uninstall
487
+ test_reinstall() {
488
+ echo "Reinstalling @blockrun/clawrouter..."
489
+ npm install -g @blockrun/clawrouter@latest
490
+
491
+ echo "Verifying reinstall works..."
492
+ clawrouter --version || return 1
493
+
494
+ return 0
495
+ }
496
+
497
+ # Test 4: Installation as OpenClaw plugin (if OpenClaw available)
498
+ test_openclaw_plugin_install() {
499
+ echo "Installing OpenClaw..."
500
+ npm install -g openclaw@latest || {
501
+ echo "OpenClaw not available, skipping test"
502
+ return 0
503
+ }
504
+
505
+ echo "Installing ClawRouter as OpenClaw plugin..."
506
+ openclaw plugins install @blockrun/clawrouter || return 1
507
+
508
+ echo "Verifying plugin is listed..."
509
+ openclaw plugins list | grep -q "clawrouter" || return 1
510
+
511
+ return 0
512
+ }
513
+
514
+ # Test 5: OpenClaw plugin uninstall
515
+ test_openclaw_plugin_uninstall() {
516
+ if ! which openclaw 2>/dev/null; then
517
+ echo "OpenClaw not available, skipping test"
518
+ return 0
519
+ fi
520
+
521
+ echo "Uninstalling ClawRouter plugin..."
522
+ openclaw plugins uninstall clawrouter || return 1
523
+
524
+ echo "Verifying plugin is removed..."
525
+ if openclaw plugins list 2>/dev/null | grep -q "clawrouter"; then
526
+ echo "ERROR: plugin still listed after uninstall"
527
+ return 1
528
+ fi
529
+
530
+ return 0
531
+ }
532
+
533
+ # Test 6: Upgrade from previous version
534
+ test_upgrade() {
535
+ echo "Installing older version (0.8.25)..."
536
+ npm install -g @blockrun/clawrouter@0.8.25
537
+
538
+ echo "Verifying old version..."
539
+ local old_version=$(clawrouter --version)
540
+ echo "Installed: $old_version"
541
+
542
+ echo "Upgrading to latest..."
543
+ npm install -g @blockrun/clawrouter@latest
544
+
545
+ echo "Verifying upgrade..."
546
+ local new_version=$(clawrouter --version)
547
+ echo "Upgraded to: $new_version"
548
+
549
+ if [ "$old_version" = "$new_version" ]; then
550
+ echo "ERROR: version did not change after upgrade"
551
+ return 1
552
+ fi
553
+
554
+ return 0
555
+ }
556
+
557
+ # Test 7: Installation with custom wallet key
558
+ test_custom_wallet() {
559
+ echo "Setting custom wallet key..."
560
+ export BLOCKRUN_WALLET_KEY="0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
561
+
562
+ echo "Installing with wallet key..."
563
+ npm install -g @blockrun/clawrouter@latest
564
+
565
+ echo "Verifying installation..."
566
+ clawrouter --version || return 1
567
+
568
+ unset BLOCKRUN_WALLET_KEY
569
+ return 0
570
+ }
571
+
572
+ # Test 8: Verify package files exist
573
+ test_package_files() {
574
+ echo "Installing @blockrun/clawrouter..."
575
+ npm install -g @blockrun/clawrouter@latest
576
+
577
+ echo "Finding package installation directory..."
578
+ local pkg_dir=$(npm root -g)/@blockrun/clawrouter
579
+
580
+ echo "Checking for required files..."
581
+ [ -f "$pkg_dir/dist/index.js" ] || { echo "Missing dist/index.js"; return 1; }
582
+ [ -f "$pkg_dir/dist/cli.js" ] || { echo "Missing dist/cli.js"; return 1; }
583
+ [ -f "$pkg_dir/package.json" ] || { echo "Missing package.json"; return 1; }
584
+ [ -f "$pkg_dir/openclaw.plugin.json" ] || { echo "Missing openclaw.plugin.json"; return 1; }
585
+
586
+ echo "All required files present"
587
+ return 0
588
+ }
589
+
590
+ # Test 9: Version command accuracy
591
+ test_version_command() {
592
+ echo "Installing @blockrun/clawrouter..."
593
+ npm install -g @blockrun/clawrouter@latest
594
+
595
+ echo "Running version command..."
596
+ local cli_version=$(clawrouter --version)
597
+
598
+ echo "Reading package.json version..."
599
+ local pkg_dir=$(npm root -g)/@blockrun/clawrouter
600
+ local pkg_version=$(node -p "require('$pkg_dir/package.json').version")
601
+
602
+ echo "CLI version: $cli_version"
603
+ echo "Package version: $pkg_version"
604
+
605
+ if [ "$cli_version" != "$pkg_version" ]; then
606
+ echo "ERROR: version mismatch"
607
+ return 1
608
+ fi
609
+
610
+ return 0
611
+ }
612
+
613
+ # Test 10: Full cleanup verification
614
+ test_full_cleanup() {
615
+ echo "Installing @blockrun/clawrouter..."
616
+ npm install -g @blockrun/clawrouter@latest
617
+
618
+ echo "Finding all ClawRouter files..."
619
+ local pkg_dir=$(npm root -g)/@blockrun/clawrouter
620
+ local bin_link=$(which clawrouter)
621
+
622
+ echo "Package dir: $pkg_dir"
623
+ echo "Binary link: $bin_link"
624
+
625
+ echo "Uninstalling..."
626
+ npm uninstall -g @blockrun/clawrouter
627
+
628
+ echo "Verifying complete cleanup..."
629
+ if [ -d "$pkg_dir" ]; then
630
+ echo "ERROR: package directory still exists: $pkg_dir"
631
+ return 1
632
+ fi
633
+
634
+ if [ -f "$bin_link" ] || [ -L "$bin_link" ]; then
635
+ echo "ERROR: binary link still exists: $bin_link"
636
+ return 1
637
+ fi
638
+
639
+ echo "Complete cleanup verified"
640
+ return 0
641
+ }
642
+
643
+ # Run all tests
644
+ echo "╔════════════════════════════════════════════════════════╗"
645
+ echo "║ ClawRouter Docker Installation Test Suite ║"
646
+ echo "╚════════════════════════════════════════════════════════╝"
647
+
648
+ test_case "1. Fresh npm global installation" test_fresh_install
649
+ test_case "2. Uninstall verification" test_uninstall
650
+ test_case "3. Reinstall after uninstall" test_reinstall
651
+ test_case "4. OpenClaw plugin installation" test_openclaw_plugin_install
652
+ test_case "5. OpenClaw plugin uninstall" test_openclaw_plugin_uninstall
653
+ test_case "6. Upgrade from previous version" test_upgrade
654
+ test_case "7. Installation with custom wallet" test_custom_wallet
655
+ test_case "8. Package files verification" test_package_files
656
+ test_case "9. Version command accuracy" test_version_command
657
+ test_case "10. Full cleanup verification" test_full_cleanup
658
+
659
+ echo ""
660
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
661
+ echo "Summary: $PASS passed, $FAIL failed"
662
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
663
+
664
+ [ $FAIL -eq 0 ] && exit 0 || exit 1
665
+ ```
666
+
667
+ ### Step 3: Make test script executable
668
+
669
+ ```bash
670
+ chmod +x test/docker-install-tests.sh
671
+ ```
672
+
673
+ ### Step 4: Update run-docker-test.sh to include installation tests
674
+
675
+ **Modify `test/run-docker-test.sh`:**
676
+
677
+ ```bash
678
+ #!/bin/bash
679
+ set -e
680
+
681
+ cd "$(dirname "$0")/.."
682
+
683
+ echo "🐳 Building Docker test environment for installation tests..."
684
+ docker build -f test/Dockerfile.install-test -t clawrouter-install-test .
685
+
686
+ echo ""
687
+ echo "🧪 Running installation test suite (10 test cases)..."
688
+ docker run --rm \
689
+ -v "$(pwd)/test/docker-install-tests.sh:/test.sh:ro" \
690
+ clawrouter-install-test \
691
+ bash -c "cp /test.sh /tmp/test.sh && chmod +x /tmp/test.sh && /tmp/test.sh"
692
+
693
+ echo ""
694
+ echo "✅ Docker installation tests completed successfully!"
695
+ ```
696
+
697
+ ### Step 5: Run Docker installation tests
698
+
699
+ Run:
700
+
701
+ ```bash
702
+ ./test/run-docker-test.sh
703
+ ```
704
+
705
+ Expected output:
706
+
707
+ ```
708
+ 🐳 Building Docker test environment for installation tests...
709
+ ...
710
+ 🧪 Running installation test suite (10 test cases)...
711
+
712
+ ╔════════════════════════════════════════════════════════╗
713
+ ║ ClawRouter Docker Installation Test Suite ║
714
+ ╚════════════════════════════════════════════════════════╝
715
+
716
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
717
+ Test: 1. Fresh npm global installation
718
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
719
+ Installing @blockrun/clawrouter globally...
720
+ Verifying clawrouter command exists...
721
+ Checking version...
722
+ 0.8.30
723
+ ✓ PASS
724
+
725
+ [... 9 more tests ...]
726
+
727
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
728
+ Summary: 10 passed, 0 failed
729
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
730
+
731
+ ✅ Docker installation tests completed successfully!
732
+ ```
733
+
734
+ ### Step 6: Commit Docker installation tests
735
+
736
+ ```bash
737
+ git add test/Dockerfile.install-test test/docker-install-tests.sh test/run-docker-test.sh
738
+ git commit -m "test: add Docker-based installation testing (10 test cases)
739
+
740
+ Test coverage:
741
+ - Fresh npm global installation
742
+ - Uninstall verification
743
+ - Reinstall after uninstall
744
+ - OpenClaw plugin install/uninstall
745
+ - Upgrade from previous version
746
+ - Custom wallet key installation
747
+ - Package files verification
748
+ - Version command accuracy
749
+ - Full cleanup verification
750
+
751
+ Validates installation, upgrade, uninstall workflows in isolated Docker
752
+ environment."
753
+ ```
754
+
755
+ ---
756
+
757
+ ## Task 3: Deployment Automation
758
+
759
+ **Goal:** Automate pre-publish validation, version bumping, npm publish, and GitHub release creation.
760
+
761
+ **Files:**
762
+
763
+ - Create: `scripts/deploy.sh`
764
+ - Modify: `package.json` (add deploy script)
765
+
766
+ ### Step 1: Create deployment script
767
+
768
+ **Create `scripts/deploy.sh`:**
769
+
770
+ ```bash
771
+ #!/bin/bash
772
+ set -e
773
+
774
+ # Colors
775
+ GREEN='\033[0;32m'
776
+ YELLOW='\033[1;33m'
777
+ RED='\033[0;31m'
778
+ NC='\033[0m' # No Color
779
+
780
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
781
+ echo -e "${GREEN} ClawRouter Deployment Pipeline${NC}"
782
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
783
+
784
+ # Step 1: Check git status (must be clean)
785
+ echo ""
786
+ echo -e "${YELLOW}1. Checking git status...${NC}"
787
+ if [[ -n $(git status --porcelain) ]]; then
788
+ echo -e "${RED}ERROR: Working directory is not clean. Commit or stash changes first.${NC}"
789
+ exit 1
790
+ fi
791
+ echo "✓ Working directory is clean"
792
+
793
+ # Step 2: Check we're on main branch
794
+ echo ""
795
+ echo -e "${YELLOW}2. Checking branch...${NC}"
796
+ BRANCH=$(git rev-parse --abbrev-ref HEAD)
797
+ if [ "$BRANCH" != "main" ]; then
798
+ echo -e "${RED}ERROR: Must be on main branch (currently on $BRANCH)${NC}"
799
+ exit 1
800
+ fi
801
+ echo "✓ On main branch"
802
+
803
+ # Step 3: Pull latest changes
804
+ echo ""
805
+ echo -e "${YELLOW}3. Pulling latest changes...${NC}"
806
+ git pull origin main
807
+ echo "✓ Up to date with origin/main"
808
+
809
+ # Step 4: Install dependencies
810
+ echo ""
811
+ echo -e "${YELLOW}4. Installing dependencies...${NC}"
812
+ npm ci
813
+ echo "✓ Dependencies installed"
814
+
815
+ # Step 5: Run typecheck
816
+ echo ""
817
+ echo -e "${YELLOW}5. Running typecheck...${NC}"
818
+ npm run typecheck
819
+ echo "✓ Typecheck passed"
820
+
821
+ # Step 6: Run build
822
+ echo ""
823
+ echo -e "${YELLOW}6. Building project...${NC}"
824
+ npm run build
825
+ echo "✓ Build successful"
826
+
827
+ # Step 7: Run tests
828
+ echo ""
829
+ echo -e "${YELLOW}7. Running tests...${NC}"
830
+
831
+ # Check if wallet key is set
832
+ if [ -z "$BLOCKRUN_WALLET_KEY" ]; then
833
+ echo -e "${YELLOW}WARNING: BLOCKRUN_WALLET_KEY not set. Skipping E2E tests.${NC}"
834
+ echo "Set BLOCKRUN_WALLET_KEY to run E2E tests during deployment."
835
+ else
836
+ echo "Running E2E tests..."
837
+ npx tsx test/test-e2e.ts
838
+ echo "✓ E2E tests passed"
839
+ fi
840
+
841
+ # Step 8: Get version bump type
842
+ echo ""
843
+ echo -e "${YELLOW}8. Version bump${NC}"
844
+ echo "Current version: $(node -p "require('./package.json').version")"
845
+ echo ""
846
+ echo "Select version bump type:"
847
+ echo " 1) patch (0.8.30 → 0.8.31)"
848
+ echo " 2) minor (0.8.30 → 0.9.0)"
849
+ echo " 3) major (0.8.30 → 1.0.0)"
850
+ echo " 4) custom"
851
+ read -p "Enter choice (1-4): " VERSION_CHOICE
852
+
853
+ case $VERSION_CHOICE in
854
+ 1)
855
+ VERSION_TYPE="patch"
856
+ ;;
857
+ 2)
858
+ VERSION_TYPE="minor"
859
+ ;;
860
+ 3)
861
+ VERSION_TYPE="major"
862
+ ;;
863
+ 4)
864
+ read -p "Enter custom version (e.g., 1.0.0-beta.1): " CUSTOM_VERSION
865
+ npm version "$CUSTOM_VERSION" --no-git-tag-version
866
+ NEW_VERSION="$CUSTOM_VERSION"
867
+ ;;
868
+ *)
869
+ echo -e "${RED}Invalid choice${NC}"
870
+ exit 1
871
+ ;;
872
+ esac
873
+
874
+ # Bump version if not custom
875
+ if [ -n "$VERSION_TYPE" ]; then
876
+ NEW_VERSION=$(npm version "$VERSION_TYPE" --no-git-tag-version)
877
+ NEW_VERSION=${NEW_VERSION#v} # Remove leading 'v'
878
+ fi
879
+
880
+ echo "✓ Version bumped to $NEW_VERSION"
881
+
882
+ # Step 9: Update version in src/version.ts
883
+ echo ""
884
+ echo -e "${YELLOW}9. Updating version in source files...${NC}"
885
+ cat > src/version.ts <<EOF
886
+ /**
887
+ * ClawRouter version
888
+ * Auto-generated during deployment
889
+ */
890
+ export const VERSION = "$NEW_VERSION";
891
+ EOF
892
+ echo "✓ Version updated in src/version.ts"
893
+
894
+ # Step 10: Rebuild with new version
895
+ echo ""
896
+ echo -e "${YELLOW}10. Rebuilding with new version...${NC}"
897
+ npm run build
898
+ echo "✓ Rebuild successful"
899
+
900
+ # Step 11: Commit version bump
901
+ echo ""
902
+ echo -e "${YELLOW}11. Committing version bump...${NC}"
903
+ git add package.json package-lock.json src/version.ts
904
+ git commit -m "$NEW_VERSION"
905
+ echo "✓ Version bump committed"
906
+
907
+ # Step 12: Create git tag
908
+ echo ""
909
+ echo -e "${YELLOW}12. Creating git tag...${NC}"
910
+ git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION"
911
+ echo "✓ Tag v$NEW_VERSION created"
912
+
913
+ # Step 13: Confirm publish
914
+ echo ""
915
+ echo -e "${YELLOW}13. Ready to publish${NC}"
916
+ echo ""
917
+ echo "Package: @blockrun/clawrouter"
918
+ echo "Version: $NEW_VERSION"
919
+ echo "Registry: https://registry.npmjs.org"
920
+ echo ""
921
+ read -p "Publish to npm? (y/N): " CONFIRM_PUBLISH
922
+
923
+ if [ "$CONFIRM_PUBLISH" != "y" ] && [ "$CONFIRM_PUBLISH" != "Y" ]; then
924
+ echo -e "${YELLOW}Publish cancelled. Version was bumped but not published.${NC}"
925
+ echo "To publish later, run: npm publish"
926
+ exit 0
927
+ fi
928
+
929
+ # Step 14: Publish to npm
930
+ echo ""
931
+ echo -e "${YELLOW}14. Publishing to npm...${NC}"
932
+ npm publish --access public
933
+ echo "✓ Published to npm"
934
+
935
+ # Step 15: Push to GitHub
936
+ echo ""
937
+ echo -e "${YELLOW}15. Pushing to GitHub...${NC}"
938
+ git push origin main
939
+ git push origin "v$NEW_VERSION"
940
+ echo "✓ Pushed to GitHub"
941
+
942
+ # Step 16: Create GitHub release
943
+ echo ""
944
+ echo -e "${YELLOW}16. Creating GitHub release...${NC}"
945
+ if command -v gh &> /dev/null; then
946
+ gh release create "v$NEW_VERSION" \
947
+ --title "v$NEW_VERSION" \
948
+ --notes "Release v$NEW_VERSION" \
949
+ --generate-notes
950
+ echo "✓ GitHub release created"
951
+ else
952
+ echo -e "${YELLOW}WARNING: gh CLI not found. Skipping GitHub release creation.${NC}"
953
+ echo "Create release manually at: https://github.com/BlockRunAI/ClawRouter/releases/new"
954
+ fi
955
+
956
+ echo ""
957
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
958
+ echo -e "${GREEN} Deployment Complete! 🎉${NC}"
959
+ echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
960
+ echo ""
961
+ echo "Package: @blockrun/clawrouter@$NEW_VERSION"
962
+ echo "npm: https://www.npmjs.com/package/@blockrun/clawrouter"
963
+ echo "GitHub: https://github.com/BlockRunAI/ClawRouter/releases/tag/v$NEW_VERSION"
964
+ echo ""
965
+ ```
966
+
967
+ ### Step 2: Make deployment script executable
968
+
969
+ ```bash
970
+ chmod +x scripts/deploy.sh
971
+ ```
972
+
973
+ ### Step 3: Add deploy command to package.json
974
+
975
+ **Modify `package.json` scripts section:**
976
+
977
+ ```json
978
+ {
979
+ "scripts": {
980
+ "build": "tsup",
981
+ "dev": "tsup --watch",
982
+ "typecheck": "tsc --noEmit",
983
+ "lint": "eslint src/",
984
+ "format": "prettier --write .",
985
+ "format:check": "prettier --check .",
986
+ "test:resilience:errors": "npx tsx test/resilience-errors.ts",
987
+ "test:resilience:stability": "DURATION_MINUTES=5 npx tsx test/resilience-stability.ts",
988
+ "test:resilience:stability:full": "DURATION_MINUTES=240 npx tsx test/resilience-stability.ts",
989
+ "test:resilience:lifecycle": "npx tsx test/resilience-lifecycle.ts",
990
+ "test:resilience:quick": "npm run test:resilience:errors && npm run test:resilience:lifecycle",
991
+ "test:resilience:full": "npm run test:resilience:errors && npm run test:resilience:lifecycle && npm run test:resilience:stability:full",
992
+ "test:e2e:tool-ids": "npx tsx test/e2e-tool-id-sanitization.ts",
993
+ "test:docker:install": "./test/run-docker-test.sh",
994
+ "deploy": "./scripts/deploy.sh"
995
+ }
996
+ }
997
+ ```
998
+
999
+ ### Step 4: Test deployment script (dry run)
1000
+
1001
+ **Before running the full deployment, test the validation steps:**
1002
+
1003
+ ```bash
1004
+ # Test git status check
1005
+ git status
1006
+
1007
+ # Test typecheck
1008
+ npm run typecheck
1009
+
1010
+ # Test build
1011
+ npm run build
1012
+
1013
+ # Test E2E (if wallet key set)
1014
+ BLOCKRUN_WALLET_KEY=0x... npx tsx test/test-e2e.ts
1015
+ ```
1016
+
1017
+ ### Step 5: Document deployment process
1018
+
1019
+ **Create `docs/deployment.md`:**
1020
+
1021
+ ````markdown
1022
+ # ClawRouter Deployment Guide
1023
+
1024
+ ## Prerequisites
1025
+
1026
+ 1. **npm account with publish access** to `@blockrun/clawrouter`
1027
+ 2. **GitHub CLI (`gh`)** installed (optional, for automated release creation)
1028
+ 3. **Funded wallet** for E2E tests (optional, but recommended)
1029
+
1030
+ ## Deployment Process
1031
+
1032
+ ### Option 1: Automated Deployment (Recommended)
1033
+
1034
+ ```bash
1035
+ # Set wallet key for E2E tests (optional)
1036
+ export BLOCKRUN_WALLET_KEY=0x...
1037
+
1038
+ # Run deployment script
1039
+ npm run deploy
1040
+ ```
1041
+ ````
1042
+
1043
+ The script will:
1044
+
1045
+ 1. ✓ Check git status is clean
1046
+ 2. ✓ Verify on main branch
1047
+ 3. ✓ Pull latest changes
1048
+ 4. ✓ Install dependencies
1049
+ 5. ✓ Run typecheck
1050
+ 6. ✓ Build project
1051
+ 7. ✓ Run E2E tests (if wallet key set)
1052
+ 8. ✓ Prompt for version bump type
1053
+ 9. ✓ Update version in package.json and src/version.ts
1054
+ 10. ✓ Rebuild with new version
1055
+ 11. ✓ Commit version bump
1056
+ 12. ✓ Create git tag
1057
+ 13. ✓ Publish to npm
1058
+ 14. ✓ Push to GitHub
1059
+ 15. ✓ Create GitHub release
1060
+
1061
+ ### Option 2: Manual Deployment
1062
+
1063
+ ```bash
1064
+ # 1. Update version
1065
+ npm version patch # or minor, or major
1066
+
1067
+ # 2. Update src/version.ts
1068
+ echo 'export const VERSION = "0.8.31";' > src/version.ts
1069
+
1070
+ # 3. Build
1071
+ npm run build
1072
+
1073
+ # 4. Commit
1074
+ git add package.json package-lock.json src/version.ts
1075
+ git commit -m "0.8.31"
1076
+ git tag -a v0.8.31 -m "Release v0.8.31"
1077
+
1078
+ # 5. Publish
1079
+ npm publish --access public
1080
+
1081
+ # 6. Push
1082
+ git push origin main
1083
+ git push origin v0.8.31
1084
+
1085
+ # 7. Create GitHub release
1086
+ gh release create v0.8.31 --title "v0.8.31" --generate-notes
1087
+ ```
1088
+
1089
+ ## Version Bump Types
1090
+
1091
+ - **patch**: Bug fixes, minor changes (0.8.30 → 0.8.31)
1092
+ - **minor**: New features, non-breaking changes (0.8.30 → 0.9.0)
1093
+ - **major**: Breaking changes (0.8.30 → 1.0.0)
1094
+ - **custom**: Pre-release versions (0.8.30 → 1.0.0-beta.1)
1095
+
1096
+ ## Post-Deployment Verification
1097
+
1098
+ 1. Check npm package: https://www.npmjs.com/package/@blockrun/clawrouter
1099
+ 2. Verify installation: `npm install -g @blockrun/clawrouter@latest`
1100
+ 3. Test version: `clawrouter --version`
1101
+ 4. Check GitHub release: https://github.com/BlockRunAI/ClawRouter/releases
1102
+
1103
+ ## Rollback
1104
+
1105
+ If deployment fails:
1106
+
1107
+ ```bash
1108
+ # Delete tag locally and remotely
1109
+ git tag -d v0.8.31
1110
+ git push origin :refs/tags/v0.8.31
1111
+
1112
+ # Revert version commit
1113
+ git revert HEAD
1114
+ git push origin main
1115
+
1116
+ # Unpublish from npm (within 72 hours)
1117
+ npm unpublish @blockrun/clawrouter@0.8.31
1118
+ ```
1119
+
1120
+ ## Troubleshooting
1121
+
1122
+ ### "Working directory is not clean"
1123
+
1124
+ Commit or stash changes before deploying:
1125
+
1126
+ ```bash
1127
+ git status
1128
+ git add .
1129
+ git commit -m "feat: ..."
1130
+ ```
1131
+
1132
+ ### "Must be on main branch"
1133
+
1134
+ Switch to main:
1135
+
1136
+ ```bash
1137
+ git checkout main
1138
+ ```
1139
+
1140
+ ### E2E tests fail
1141
+
1142
+ Set wallet key:
1143
+
1144
+ ```bash
1145
+ export BLOCKRUN_WALLET_KEY=0x...
1146
+ ```
1147
+
1148
+ Or skip E2E tests (not recommended):
1149
+
1150
+ ```bash
1151
+ # Edit scripts/deploy.sh and comment out E2E test section
1152
+ ```
1153
+
1154
+ ````
1155
+
1156
+ ### Step 6: Run deployment script (test mode)
1157
+
1158
+ **Test the deployment script without publishing:**
1159
+
1160
+ ```bash
1161
+ # Comment out the npm publish and git push steps in scripts/deploy.sh
1162
+ # Then run:
1163
+ npm run deploy
1164
+
1165
+ # Select patch version bump
1166
+ # Review all steps
1167
+ # Decline publish when prompted
1168
+ ````
1169
+
1170
+ Expected output:
1171
+
1172
+ ```
1173
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1174
+ ClawRouter Deployment Pipeline
1175
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1176
+
1177
+ 1. Checking git status...
1178
+ ✓ Working directory is clean
1179
+
1180
+ 2. Checking branch...
1181
+ ✓ On main branch
1182
+
1183
+ 3. Pulling latest changes...
1184
+ ✓ Up to date with origin/main
1185
+
1186
+ 4. Installing dependencies...
1187
+ ✓ Dependencies installed
1188
+
1189
+ 5. Running typecheck...
1190
+ ✓ Typecheck passed
1191
+
1192
+ 6. Building project...
1193
+ ✓ Build successful
1194
+
1195
+ 7. Running tests...
1196
+ ✓ E2E tests passed
1197
+
1198
+ 8. Version bump
1199
+ Current version: 0.8.30
1200
+
1201
+ Select version bump type:
1202
+ 1) patch (0.8.30 → 0.8.31)
1203
+ 2) minor (0.8.30 → 0.9.0)
1204
+ 3) major (0.8.30 → 1.0.0)
1205
+ 4) custom
1206
+ Enter choice (1-4): 1
1207
+ ✓ Version bumped to 0.8.31
1208
+
1209
+ 9. Updating version in source files...
1210
+ ✓ Version updated in src/version.ts
1211
+
1212
+ 10. Rebuilding with new version...
1213
+ ✓ Rebuild successful
1214
+
1215
+ 11. Committing version bump...
1216
+ ✓ Version bump committed
1217
+
1218
+ 12. Creating git tag...
1219
+ ✓ Tag v0.8.31 created
1220
+
1221
+ 13. Ready to publish
1222
+
1223
+ Package: @blockrun/clawrouter
1224
+ Version: 0.8.31
1225
+ Registry: https://registry.npmjs.org
1226
+
1227
+ Publish to npm? (y/N): N
1228
+ Publish cancelled. Version was bumped but not published.
1229
+ To publish later, run: npm publish
1230
+ ```
1231
+
1232
+ ### Step 7: Commit deployment automation
1233
+
1234
+ ```bash
1235
+ git add scripts/deploy.sh package.json docs/deployment.md
1236
+ git commit -m "chore: add automated deployment pipeline
1237
+
1238
+ - Add deployment script with pre-publish validation
1239
+ - Version bump with interactive selection
1240
+ - Automatic git tag creation
1241
+ - npm publish with confirmation
1242
+ - GitHub release creation (requires gh CLI)
1243
+ - Add deployment documentation
1244
+
1245
+ Usage: npm run deploy"
1246
+ ```
1247
+
1248
+ ---
1249
+
1250
+ ## Execution Handoff
1251
+
1252
+ Plan complete and saved to `docs/plans/2026-02-13-e2e-docker-deployment.md`.
1253
+
1254
+ **Two execution options:**
1255
+
1256
+ **1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
1257
+
1258
+ **2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
1259
+
1260
+ **Which approach would you prefer, Your Majesty?**