@covibes/zeroshot 4.1.2 → 4.1.4

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/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## [4.1.4](https://github.com/covibes/zeroshot/compare/v4.1.3...v4.1.4) (2026-01-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ci:** enforce CI testing for all releases ([3fad703](https://github.com/covibes/zeroshot/commit/3fad703fb6eb6b7ac8f98400466bac92329c8561))
7
+
8
+ ## [4.1.3](https://github.com/covibes/zeroshot/compare/v4.1.2...v4.1.3) (2026-01-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **package:** include scripts/ in npm package files ([4bd5991](https://github.com/covibes/zeroshot/commit/4bd599163df5c7b7f9309f07c4af4b1ec5e7bf38))
14
+ * **preflight:** support macOS Keychain auth detection ([#35](https://github.com/covibes/zeroshot/issues/35)) ([a6f0880](https://github.com/covibes/zeroshot/commit/a6f08807eb2b44b241800a2240a76bf6dbedceca))
15
+
1
16
  ## [4.1.2](https://github.com/covibes/zeroshot/compare/v4.1.1...v4.1.2) (2026-01-05)
2
17
 
3
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@covibes/zeroshot",
3
- "version": "4.1.2",
3
+ "version": "4.1.4",
4
4
  "description": "Multi-agent orchestration engine for Claude - cluster coordinator and CLI",
5
5
  "main": "src/orchestrator.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "test": "mocha 'tests/**/*.test.js'",
15
15
  "test:coverage": "c8 npm test",
16
16
  "test:coverage:report": "c8 --reporter=html npm test && echo 'Coverage report generated at coverage/index.html'",
17
+ "postinstall": "node scripts/fix-node-pty-permissions.js",
17
18
  "start": "node cli/index.js",
18
19
  "typecheck": "tsc --noEmit",
19
20
  "lint": "eslint .",
@@ -81,6 +82,7 @@
81
82
  "cluster-templates/",
82
83
  "hooks/",
83
84
  "docker/",
85
+ "scripts/",
84
86
  "README.md",
85
87
  "LICENSE",
86
88
  "CHANGELOG.md"
@@ -94,7 +96,7 @@
94
96
  "chalk": "^4.1.2",
95
97
  "commander": "^14.0.2",
96
98
  "md-to-pdf": "^5.2.5",
97
- "node-pty": "^1.0.0",
99
+ "node-pty": "^1.1.0",
98
100
  "omelette": "^0.4.17",
99
101
  "pidusage": "^4.0.1",
100
102
  "proper-lockfile": "^4.1.2"
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fix node-pty spawn-helper permissions.
4
+ *
5
+ * node-pty prebuilds ship with spawn-helper lacking execute permission (mode 644).
6
+ * This causes "posix_spawnp failed" errors on macOS/Linux.
7
+ *
8
+ * Upstream bug: https://github.com/microsoft/node-pty/issues/XXX
9
+ * (File issue if not already reported)
10
+ *
11
+ * This script runs as postinstall to fix it automatically.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+
17
+ // Skip on Windows - chmod doesn't apply
18
+ if (process.platform === 'win32') {
19
+ process.exit(0);
20
+ }
21
+
22
+ const prebuildsDir = path.join(__dirname, '..', 'node_modules', 'node-pty', 'prebuilds');
23
+
24
+ if (!fs.existsSync(prebuildsDir)) {
25
+ // node-pty not installed yet or using compiled version
26
+ process.exit(0);
27
+ }
28
+
29
+ let fixed = 0;
30
+ let errors = 0;
31
+
32
+ try {
33
+ const platforms = fs.readdirSync(prebuildsDir).filter(f => {
34
+ try {
35
+ return fs.statSync(path.join(prebuildsDir, f)).isDirectory();
36
+ } catch {
37
+ return false;
38
+ }
39
+ });
40
+
41
+ for (const platform of platforms) {
42
+ // Only fix Unix platforms (darwin, linux)
43
+ if (!platform.startsWith('darwin') && !platform.startsWith('linux')) {
44
+ continue;
45
+ }
46
+
47
+ const helper = path.join(prebuildsDir, platform, 'spawn-helper');
48
+ try {
49
+ if (!fs.existsSync(helper)) continue;
50
+
51
+ const stat = fs.statSync(helper);
52
+ // Check if not executable (missing user execute bit)
53
+ if (!(stat.mode & 0o100)) {
54
+ fs.chmodSync(helper, 0o755);
55
+ fixed++;
56
+ }
57
+ } catch (err) {
58
+ // Log but don't fail install - permission fix is best-effort
59
+ console.warn(`[postinstall] Warning: Could not fix ${helper}: ${err.message}`);
60
+ errors++;
61
+ }
62
+ }
63
+ } catch (err) {
64
+ // Don't fail install on unexpected errors
65
+ console.warn(`[postinstall] Warning: node-pty permission fix failed: ${err.message}`);
66
+ process.exit(0);
67
+ }
68
+
69
+ if (fixed > 0) {
70
+ console.log(`[postinstall] Fixed node-pty spawn-helper permissions (${fixed} platform(s))`);
71
+ }
72
+
73
+ if (errors > 0) {
74
+ console.warn(`[postinstall] ${errors} platform(s) could not be fixed - may need manual chmod`);
75
+ }
@@ -0,0 +1,279 @@
1
+ #!/bin/bash
2
+ # record-demo.sh - Create ephemeral project, run zeroshot demo, record it, cleanup
3
+ #
4
+ # Usage:
5
+ # ./scripts/record-demo.sh # Interactive mode (you run zeroshot manually)
6
+ # ./scripts/record-demo.sh --record # Record with asciinema automatically
7
+ #
8
+ # Output: zeroshot-demo.cast (asciinema recording)
9
+ # Convert to gif: agg zeroshot-demo.cast zeroshot-demo.gif --idle-time-limit 2
10
+
11
+ set -e
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15
+ DEMO_DIR=""
16
+ RECORD_MODE=false
17
+ FORCE_MODE=false
18
+ LOCK_FILE="/tmp/zeroshot-demo-recording.lock"
19
+ CAST_FILE="$REPO_ROOT/zeroshot-demo.cast"
20
+
21
+ # Check for existing recording session
22
+ check_existing_session() {
23
+ # Check lock file
24
+ if [[ -f "$LOCK_FILE" ]]; then
25
+ local old_pid
26
+ old_pid=$(cat "$LOCK_FILE" 2>/dev/null || echo "")
27
+ if [[ -n "$old_pid" ]] && kill -0 "$old_pid" 2>/dev/null; then
28
+ echo "ERROR: Recording already in progress (PID $old_pid)"
29
+ echo "Kill it with: kill $old_pid"
30
+ exit 1
31
+ fi
32
+ # Stale lock file
33
+ rm -f "$LOCK_FILE"
34
+ fi
35
+
36
+ # Check for any asciinema processes recording to our file
37
+ local existing_asciinema
38
+ existing_asciinema=$(pgrep -f "asciinema.*zeroshot-demo.cast" 2>/dev/null || true)
39
+ if [[ -n "$existing_asciinema" ]]; then
40
+ echo "ERROR: Existing asciinema process(es) found: $existing_asciinema"
41
+ echo "Kill them with: pkill -f 'asciinema.*zeroshot-demo.cast'"
42
+ exit 1
43
+ fi
44
+
45
+ # Check for any running zeroshot clusters
46
+ local existing_zeroshot
47
+ existing_zeroshot=$(pgrep -f "zeroshot.*rate limiting" 2>/dev/null || true)
48
+ if [[ -n "$existing_zeroshot" ]]; then
49
+ echo "ERROR: Existing zeroshot process(es) found: $existing_zeroshot"
50
+ echo "Kill them with: pkill -f 'zeroshot.*rate limiting'"
51
+ exit 1
52
+ fi
53
+ }
54
+
55
+ # Parse args
56
+ while [[ $# -gt 0 ]]; do
57
+ case "$1" in
58
+ --record) RECORD_MODE=true; shift ;;
59
+ --force|-f) FORCE_MODE=true; shift ;;
60
+ *) echo "Unknown option: $1"; exit 1 ;;
61
+ esac
62
+ done
63
+
64
+ # Cleanup on exit
65
+ cleanup() {
66
+ # Remove lock file
67
+ rm -f "$LOCK_FILE"
68
+
69
+ if [[ -n "$DEMO_DIR" && -d "$DEMO_DIR" ]]; then
70
+ echo ""
71
+ echo "Cleaning up $DEMO_DIR..."
72
+ rm -rf "$DEMO_DIR"
73
+ echo "Done."
74
+ fi
75
+ }
76
+ trap cleanup EXIT
77
+
78
+ # Check for conflicts before doing anything
79
+ check_existing_session
80
+
81
+ # Create temp project
82
+ DEMO_DIR=$(mktemp -d -t zeroshot-demo-XXXXXX)
83
+ echo "Creating demo project in $DEMO_DIR"
84
+
85
+ cd "$DEMO_DIR"
86
+
87
+ # Initialize package.json
88
+ cat > package.json << 'EOF'
89
+ {
90
+ "name": "demo-api",
91
+ "version": "1.0.0",
92
+ "type": "commonjs",
93
+ "scripts": {
94
+ "dev": "ts-node src/index.ts",
95
+ "build": "tsc",
96
+ "start": "node dist/index.js"
97
+ }
98
+ }
99
+ EOF
100
+
101
+ # Install dependencies (quiet)
102
+ echo "Installing dependencies..."
103
+ npm install --silent express typescript ts-node @types/express @types/node
104
+
105
+ # Create tsconfig
106
+ cat > tsconfig.json << 'EOF'
107
+ {
108
+ "compilerOptions": {
109
+ "target": "ES2020",
110
+ "module": "commonjs",
111
+ "strict": true,
112
+ "esModuleInterop": true,
113
+ "outDir": "dist",
114
+ "rootDir": "src"
115
+ },
116
+ "include": ["src/**/*"]
117
+ }
118
+ EOF
119
+
120
+ # Create minimal server with users "database"
121
+ mkdir -p src
122
+
123
+ cat > src/db.ts << 'EOF'
124
+ // Simple in-memory database
125
+ export interface User {
126
+ id: number;
127
+ username: string;
128
+ email: string;
129
+ password_hash: string;
130
+ avatar: string;
131
+ created_at: Date;
132
+ }
133
+
134
+ export const users: User[] = [
135
+ {
136
+ id: 1,
137
+ username: "alice",
138
+ email: "alice@example.com",
139
+ password_hash: "$2b$10$X7VYKzPQ...",
140
+ avatar: "https://example.com/alice.jpg",
141
+ created_at: new Date("2024-01-15"),
142
+ },
143
+ {
144
+ id: 2,
145
+ username: "bob",
146
+ email: "bob@example.com",
147
+ password_hash: "$2b$10$Y8WZLaQR...",
148
+ avatar: "https://example.com/bob.jpg",
149
+ created_at: new Date("2024-02-20"),
150
+ },
151
+ ];
152
+ EOF
153
+
154
+ cat > src/index.ts << 'EOF'
155
+ import express from "express";
156
+
157
+ const app = express();
158
+ const PORT = 3000;
159
+
160
+ app.use(express.json());
161
+
162
+ // Health check
163
+ app.get("/health", (_, res) => {
164
+ res.json({ status: "ok" });
165
+ });
166
+
167
+ app.listen(PORT, () => {
168
+ console.log(`Server running on http://localhost:${PORT}`);
169
+ });
170
+ EOF
171
+
172
+ # Initialize git repo
173
+ git init --quiet
174
+ git add -A
175
+ git commit --quiet -m "Initial commit: Express API with users database"
176
+
177
+ echo ""
178
+ echo "=========================================="
179
+ echo "Demo project ready!"
180
+ echo "=========================================="
181
+ echo ""
182
+ echo "Directory: $DEMO_DIR"
183
+ echo ""
184
+ echo "Suggested demo task:"
185
+ echo " zeroshot 'Add PUT /users/:id endpoint to update user profile'"
186
+ echo ""
187
+
188
+ if [[ "$RECORD_MODE" == "true" ]]; then
189
+ echo "Recording with asciinema..."
190
+ echo ""
191
+
192
+ # Check asciinema is installed
193
+ if ! command -v asciinema &> /dev/null; then
194
+ echo "Error: asciinema not installed. Run: pip install asciinema"
195
+ exit 1
196
+ fi
197
+
198
+ # Warn if cast file exists (skip with --force)
199
+ if [[ -f "$CAST_FILE" ]] && [[ "$FORCE_MODE" != "true" ]]; then
200
+ echo "WARNING: $CAST_FILE already exists!"
201
+ echo "Previous recording will be OVERWRITTEN."
202
+ echo ""
203
+ read -p "Continue? [y/N] " -n 1 -r
204
+ echo ""
205
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
206
+ echo "Aborted."
207
+ exit 1
208
+ fi
209
+ fi
210
+
211
+ # Create lock file with our PID
212
+ echo $$ > "$LOCK_FILE"
213
+
214
+ # Record the session
215
+ # CRITICAL: Use bash -l -c to get full PATH (includes npm global binaries)
216
+ ZEROSHOT_PATH=$(which zeroshot)
217
+ TASK="Add rate limiting middleware: sliding window algorithm (not fixed window), per-IP tracking with in-memory store and automatic TTL cleanup to prevent memory leaks, configurable limits per endpoint. Return 429 Too Many Requests with Retry-After header (seconds until reset) and X-RateLimit-Remaining header on ALL responses. Must handle both IPv4 and IPv6, normalizing IPv6 to consistent format."
218
+
219
+ # SIGNAL ISOLATION: Use setsid to create new session, completely immune to terminal signals
220
+ # The recording process will NOT be killed by Ctrl+C or signals to this script
221
+ # We wait for it explicitly and only cleanup AFTER it naturally completes
222
+ echo "Starting recording in isolated session (immune to Ctrl+C)..."
223
+ echo "To kill it manually: pkill -f 'asciinema.*zeroshot-demo.cast'"
224
+ echo ""
225
+
226
+ # Disable cleanup trap during recording - we'll handle it manually
227
+ trap - EXIT
228
+
229
+ # Start asciinema in new session (setsid) so it's immune to our signals
230
+ # Save the session leader PID so we can wait for it
231
+ setsid bash -c "
232
+ # Ignore all signals - this recording WILL NOT DIE
233
+ trap '' INT TERM HUP QUIT
234
+
235
+ cd '$DEMO_DIR'
236
+ asciinema rec \
237
+ --overwrite \
238
+ --title 'Zeroshot Demo' \
239
+ --command \"bash -l -c '$ZEROSHOT_PATH \\\"$TASK\\\"'\" \
240
+ '$CAST_FILE'
241
+ " &
242
+ RECORDING_PID=$!
243
+
244
+ # Update lock file with the actual recording PID
245
+ echo $RECORDING_PID > "$LOCK_FILE"
246
+
247
+ echo "Recording started (session PID: $RECORDING_PID)"
248
+ echo "Waiting for recording to complete..."
249
+ echo ""
250
+
251
+ # Wait for recording to finish - this script will NOT kill it on Ctrl+C
252
+ # Ignore signals while waiting
253
+ trap '' INT TERM HUP
254
+ wait $RECORDING_PID 2>/dev/null || true
255
+
256
+ # Recording finished naturally - now cleanup
257
+ echo ""
258
+ echo "Recording saved to: $CAST_FILE"
259
+ echo ""
260
+ echo "Convert to gif with:"
261
+ echo " agg $CAST_FILE $REPO_ROOT/zeroshot-demo.gif --idle-time-limit 2"
262
+
263
+ # Now do cleanup
264
+ rm -f "$LOCK_FILE"
265
+ if [[ -n "$DEMO_DIR" && -d "$DEMO_DIR" ]]; then
266
+ echo ""
267
+ echo "Cleaning up $DEMO_DIR..."
268
+ rm -rf "$DEMO_DIR"
269
+ echo "Done."
270
+ fi
271
+ else
272
+ echo "Interactive mode - run zeroshot manually:"
273
+ echo ""
274
+ echo " cd $DEMO_DIR"
275
+ echo " zeroshot 'Add PUT /users/:id endpoint to update user profile'"
276
+ echo ""
277
+ echo "Press Enter when done to cleanup..."
278
+ read -r
279
+ fi
@@ -0,0 +1,40 @@
1
+ #!/bin/bash
2
+ # Test zeroshot installation on any platform
3
+ # Run: ./scripts/test-install.sh
4
+
5
+ set -e
6
+
7
+ echo "=== Zeroshot Install Test ==="
8
+ echo "Platform: $(uname -s) $(uname -m)"
9
+ echo "Node: $(node --version)"
10
+ echo "npm: $(npm --version)"
11
+ echo ""
12
+
13
+ echo "1. Installing dependencies..."
14
+ npm install
15
+
16
+ echo ""
17
+ echo "2. Linking CLI..."
18
+ npm link
19
+
20
+ echo ""
21
+ echo "3. Testing CLI commands..."
22
+
23
+ echo " zeroshot --version"
24
+ zeroshot --version
25
+
26
+ echo ""
27
+ echo " zeroshot --help"
28
+ zeroshot --help | head -20
29
+
30
+ echo ""
31
+ echo " zeroshot list"
32
+ zeroshot list 2>/dev/null || echo " (no clusters yet - this is expected)"
33
+
34
+ echo ""
35
+ echo " zeroshot config list"
36
+ zeroshot config list
37
+
38
+ echo ""
39
+ echo "=== ALL TESTS PASSED ==="
40
+ echo "Zeroshot installed successfully on $(uname -s)!"
package/src/preflight.js CHANGED
@@ -84,9 +84,31 @@ function getClaudeVersion() {
84
84
  }
85
85
  }
86
86
 
87
+ /**
88
+ * Check macOS Keychain for Claude Code credentials
89
+ * @returns {{ authenticated: boolean, error: string | null }}
90
+ */
91
+ function checkMacOsKeychain() {
92
+ if (os.platform() !== 'darwin') {
93
+ return { authenticated: false, error: 'Not macOS' };
94
+ }
95
+
96
+ try {
97
+ // Check if Claude Code credentials exist in Keychain
98
+ execSync('security find-generic-password -s "Claude Code-credentials"', {
99
+ encoding: 'utf8',
100
+ stdio: 'pipe',
101
+ timeout: 2000,
102
+ });
103
+ return { authenticated: true, error: null };
104
+ } catch {
105
+ return { authenticated: false, error: 'No credentials in Keychain' };
106
+ }
107
+ }
108
+
87
109
  /**
88
110
  * Check Claude CLI authentication status
89
- * @returns {{ authenticated: boolean, error: string | null, configDir: string }}
111
+ * @returns {{ authenticated: boolean, error: string | null, configDir: string, method?: string }}
90
112
  */
91
113
  function checkClaudeAuth() {
92
114
  const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
@@ -94,6 +116,20 @@ function checkClaudeAuth() {
94
116
 
95
117
  // Check if credentials file exists
96
118
  if (!fs.existsSync(credentialsPath)) {
119
+ // No credentials file - check macOS Keychain as fallback
120
+ // Only use Keychain when using default config dir (not custom CLAUDE_CONFIG_DIR)
121
+ const isDefaultConfigDir = !process.env.CLAUDE_CONFIG_DIR;
122
+ if (isDefaultConfigDir) {
123
+ const keychainResult = checkMacOsKeychain();
124
+ if (keychainResult.authenticated) {
125
+ return {
126
+ authenticated: true,
127
+ error: null,
128
+ configDir,
129
+ method: 'keychain',
130
+ };
131
+ }
132
+ }
97
133
  return {
98
134
  authenticated: false,
99
135
  error: 'No credentials file found',
@@ -38,7 +38,7 @@ export function listTasks(options = {}) {
38
38
 
39
39
  const statusColor =
40
40
  {
41
- running: chalk.blue,
41
+ running: chalk.green,
42
42
  completed: chalk.green,
43
43
  failed: chalk.red,
44
44
  stale: chalk.yellow,
@@ -75,7 +75,7 @@ export function listTasks(options = {}) {
75
75
 
76
76
  const statusColor =
77
77
  {
78
- running: chalk.blue,
78
+ running: chalk.green,
79
79
  completed: chalk.green,
80
80
  failed: chalk.red,
81
81
  stale: chalk.yellow,
@@ -18,7 +18,7 @@ export function showStatus(taskId) {
18
18
 
19
19
  const statusColor =
20
20
  {
21
- running: chalk.blue,
21
+ running: chalk.green,
22
22
  completed: chalk.green,
23
23
  failed: chalk.red,
24
24
  }[task.status] || chalk.yellow;
package/task-lib/tui.js CHANGED
@@ -283,7 +283,7 @@ class TaskTUI {
283
283
  const items = this.tasks.map((task) => {
284
284
  const statusIcon =
285
285
  {
286
- running: '{blue-fg}●{/}',
286
+ running: '{green-fg}●{/}',
287
287
  completed: '{green-fg}●{/}',
288
288
  failed: '{red-fg}●{/}',
289
289
  stale: '{yellow-fg}●{/}',
@@ -372,7 +372,7 @@ class TaskTUI {
372
372
 
373
373
  getStatusColor(status) {
374
374
  const colors = {
375
- running: '{blue-fg}running{/}',
375
+ running: '{green-fg}running{/}',
376
376
  completed: '{green-fg}completed{/}',
377
377
  failed: '{red-fg}failed{/}',
378
378
  stale: '{yellow-fg}stale{/}',