@covibes/zeroshot 5.3.0 → 5.4.1

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 (119) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -385
  3. package/cli/index.js +108 -4615
  4. package/package.json +16 -125
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -150
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -253
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -697
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -152
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -212
  21. package/lib/id-detector.js +0 -53
  22. package/lib/provider-detection.js +0 -59
  23. package/lib/provider-names.js +0 -56
  24. package/lib/settings.js +0 -405
  25. package/lib/stream-json-parser.js +0 -10
  26. package/scripts/fix-node-pty-permissions.js +0 -75
  27. package/scripts/record-demo.sh +0 -279
  28. package/scripts/test-install.sh +0 -40
  29. package/scripts/validate-templates.js +0 -100
  30. package/src/agent/agent-config.js +0 -193
  31. package/src/agent/agent-context-builder.js +0 -335
  32. package/src/agent/agent-hook-executor.js +0 -402
  33. package/src/agent/agent-lifecycle.js +0 -683
  34. package/src/agent/agent-stuck-detector.js +0 -256
  35. package/src/agent/agent-task-executor.js +0 -1431
  36. package/src/agent/agent-trigger-evaluator.js +0 -67
  37. package/src/agent/output-extraction.js +0 -200
  38. package/src/agent/output-reformatter.js +0 -175
  39. package/src/agent/schema-utils.js +0 -111
  40. package/src/agent-wrapper.js +0 -570
  41. package/src/agents/git-pusher-agent.json +0 -20
  42. package/src/attach/attach-client.js +0 -438
  43. package/src/attach/attach-server.js +0 -543
  44. package/src/attach/index.js +0 -35
  45. package/src/attach/protocol.js +0 -220
  46. package/src/attach/ring-buffer.js +0 -121
  47. package/src/attach/socket-discovery.js +0 -267
  48. package/src/claude-task-runner.js +0 -523
  49. package/src/config-router.js +0 -80
  50. package/src/config-validator.js +0 -1660
  51. package/src/copy-worker.js +0 -43
  52. package/src/github.js +0 -139
  53. package/src/isolation-manager.js +0 -1381
  54. package/src/ledger.js +0 -614
  55. package/src/logic-engine.js +0 -223
  56. package/src/message-bus-bridge.js +0 -139
  57. package/src/message-bus.js +0 -249
  58. package/src/name-generator.js +0 -232
  59. package/src/orchestrator.js +0 -2519
  60. package/src/preflight.js +0 -485
  61. package/src/process-metrics.js +0 -566
  62. package/src/providers/anthropic/cli-builder.js +0 -45
  63. package/src/providers/anthropic/index.js +0 -134
  64. package/src/providers/anthropic/models.js +0 -23
  65. package/src/providers/anthropic/output-parser.js +0 -159
  66. package/src/providers/base-provider.js +0 -181
  67. package/src/providers/capabilities.js +0 -51
  68. package/src/providers/google/cli-builder.js +0 -55
  69. package/src/providers/google/index.js +0 -116
  70. package/src/providers/google/models.js +0 -24
  71. package/src/providers/google/output-parser.js +0 -92
  72. package/src/providers/index.js +0 -75
  73. package/src/providers/openai/cli-builder.js +0 -122
  74. package/src/providers/openai/index.js +0 -135
  75. package/src/providers/openai/models.js +0 -21
  76. package/src/providers/openai/output-parser.js +0 -129
  77. package/src/schemas/sub-cluster.js +0 -156
  78. package/src/status-footer.js +0 -980
  79. package/src/sub-cluster-wrapper.js +0 -560
  80. package/src/task-runner.js +0 -30
  81. package/src/template-resolver.js +0 -369
  82. package/src/tui/CHANGES.txt +0 -133
  83. package/src/tui/LAYOUT.md +0 -261
  84. package/src/tui/README.txt +0 -192
  85. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  86. package/src/tui/data-poller.js +0 -325
  87. package/src/tui/demo.js +0 -208
  88. package/src/tui/formatters.js +0 -128
  89. package/src/tui/index.js +0 -193
  90. package/src/tui/keybindings.js +0 -383
  91. package/src/tui/layout.js +0 -334
  92. package/src/tui/renderer.js +0 -194
  93. package/task-lib/attachable-watcher.js +0 -272
  94. package/task-lib/claude-recovery.js +0 -119
  95. package/task-lib/commands/clean.js +0 -50
  96. package/task-lib/commands/get-log-path.js +0 -23
  97. package/task-lib/commands/kill.js +0 -32
  98. package/task-lib/commands/list.js +0 -105
  99. package/task-lib/commands/logs.js +0 -411
  100. package/task-lib/commands/resume.js +0 -42
  101. package/task-lib/commands/run.js +0 -57
  102. package/task-lib/commands/schedule.js +0 -105
  103. package/task-lib/commands/scheduler-cmd.js +0 -96
  104. package/task-lib/commands/schedules.js +0 -98
  105. package/task-lib/commands/status.js +0 -44
  106. package/task-lib/commands/unschedule.js +0 -16
  107. package/task-lib/completion.js +0 -9
  108. package/task-lib/config.js +0 -10
  109. package/task-lib/name-generator.js +0 -230
  110. package/task-lib/package.json +0 -3
  111. package/task-lib/runner.js +0 -144
  112. package/task-lib/scheduler.js +0 -252
  113. package/task-lib/store.js +0 -230
  114. package/task-lib/tui/formatters.js +0 -166
  115. package/task-lib/tui/index.js +0 -197
  116. package/task-lib/tui/layout.js +0 -111
  117. package/task-lib/tui/renderer.js +0 -119
  118. package/task-lib/tui.js +0 -384
  119. package/task-lib/watcher.js +0 -188
package/lib/settings.js DELETED
@@ -1,405 +0,0 @@
1
- /**
2
- * Settings management for zeroshot
3
- * Persistent user preferences stored in ~/.zeroshot/settings.json
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const os = require('os');
9
- const { validateMountConfig, validateEnvPassthrough } = require('./docker-config');
10
- const {
11
- VALID_PROVIDERS,
12
- normalizeProviderName,
13
- normalizeProviderSettings,
14
- } = require('./provider-names');
15
-
16
- /**
17
- * Get settings file path (dynamically reads env var for testing)
18
- * Using a getter ensures tests can override the path at runtime
19
- * @returns {string}
20
- */
21
- function getSettingsFile() {
22
- return (
23
- process.env.ZEROSHOT_SETTINGS_FILE || path.join(os.homedir(), '.zeroshot', 'settings.json')
24
- );
25
- }
26
-
27
- /**
28
- * Model hierarchy for cost ceiling validation
29
- * Higher number = more expensive/capable model
30
- */
31
- const MODEL_HIERARCHY = {
32
- opus: 3,
33
- sonnet: 2,
34
- haiku: 1,
35
- };
36
-
37
- const VALID_MODELS = Object.keys(MODEL_HIERARCHY);
38
- const LEVEL_RANKS = { level1: 1, level2: 2, level3: 3 };
39
-
40
- /**
41
- * Validate a requested model against the maxModel ceiling and minModel floor
42
- * @param {string} requestedModel - Model the agent wants to use
43
- * @param {string} maxModel - Maximum allowed model (cost ceiling)
44
- * @param {string|null} minModel - Minimum required model (cost floor)
45
- * @returns {string} The validated model
46
- * @throws {Error} If requested model exceeds ceiling or falls below floor
47
- */
48
- function validateModelAgainstMax(requestedModel, maxModel, minModel = null) {
49
- if (!requestedModel) return maxModel; // Default to ceiling if unspecified
50
-
51
- if (!VALID_MODELS.includes(requestedModel)) {
52
- throw new Error(`Invalid model "${requestedModel}". Valid: ${VALID_MODELS.join(', ')}`);
53
- }
54
- if (!VALID_MODELS.includes(maxModel)) {
55
- throw new Error(`Invalid maxModel "${maxModel}". Valid: ${VALID_MODELS.join(', ')}`);
56
- }
57
-
58
- if (MODEL_HIERARCHY[requestedModel] > MODEL_HIERARCHY[maxModel]) {
59
- throw new Error(
60
- `Agent requests "${requestedModel}" but maxModel is "${maxModel}". ` +
61
- `Either lower agent's model or raise maxModel.`
62
- );
63
- }
64
-
65
- if (minModel) {
66
- if (!VALID_MODELS.includes(minModel)) {
67
- throw new Error(`Invalid minModel "${minModel}". Valid: ${VALID_MODELS.join(', ')}`);
68
- }
69
- if (MODEL_HIERARCHY[minModel] > MODEL_HIERARCHY[maxModel]) {
70
- throw new Error(`minModel "${minModel}" cannot be higher than maxModel "${maxModel}".`);
71
- }
72
- if (MODEL_HIERARCHY[requestedModel] < MODEL_HIERARCHY[minModel]) {
73
- throw new Error(
74
- `Agent requests "${requestedModel}" but minModel is "${minModel}". ` +
75
- `Either raise agent's model or lower minModel.`
76
- );
77
- }
78
- }
79
-
80
- return requestedModel;
81
- }
82
-
83
- // Default settings
84
- const DEFAULT_SETTINGS = {
85
- maxModel: 'opus', // Cost ceiling - agents cannot use models above this
86
- minModel: null, // Cost floor - agents cannot use models below this (null = no minimum)
87
- defaultProvider: 'claude',
88
- providerSettings: {
89
- claude: {
90
- maxLevel: 'level3',
91
- minLevel: 'level1',
92
- defaultLevel: 'level2',
93
- levelOverrides: {},
94
- },
95
- codex: {
96
- maxLevel: 'level3',
97
- minLevel: 'level1',
98
- defaultLevel: 'level2',
99
- levelOverrides: {},
100
- },
101
- gemini: {
102
- maxLevel: 'level3',
103
- minLevel: 'level1',
104
- defaultLevel: 'level2',
105
- levelOverrides: {},
106
- },
107
- },
108
- defaultConfig: 'conductor-bootstrap',
109
- defaultDocker: false,
110
- strictSchema: true, // true = reliable json output (default), false = live streaming (may crash - see bold-meadow-11)
111
- logLevel: 'normal',
112
- // Auto-update settings
113
- autoCheckUpdates: true, // Check npm registry for newer versions
114
- lastUpdateCheckAt: null, // Unix timestamp of last check (null = never checked)
115
- lastSeenVersion: null, // Don't re-prompt for same version
116
- // Claude command - customize how to invoke Claude CLI (default: 'claude')
117
- // Example: 'ccr code' for claude-code-router integration
118
- claudeCommand: 'claude',
119
- // Docker isolation mounts - preset names or {host, container, readonly?} objects
120
- // Valid presets: gh, git, ssh, aws, azure, kube, terraform, gcloud, claude, codex, gemini
121
- dockerMounts: ['gh', 'git', 'ssh'],
122
- // Extra env vars to pass to Docker container (in addition to preset-implied ones)
123
- // Supports: VAR (if set), VAR_* (pattern), VAR=value (forced), VAR= (empty)
124
- dockerEnvPassthrough: [],
125
- // Container home directory - where $HOME resolves in container paths
126
- // Default: /home/node (matches zeroshot-cluster-base image)
127
- dockerContainerHome: '/home/node',
128
- };
129
-
130
- function mapLegacyModelToLevel(model) {
131
- switch (model) {
132
- case 'haiku':
133
- return 'level1';
134
- case 'sonnet':
135
- return 'level2';
136
- case 'opus':
137
- return 'level3';
138
- default:
139
- return null;
140
- }
141
- }
142
-
143
- function mergeProviderSettings(current, overrides) {
144
- const merged = { ...current };
145
- for (const provider of VALID_PROVIDERS) {
146
- merged[provider] = {
147
- ...current[provider],
148
- ...(overrides?.[provider] || {}),
149
- };
150
- if (!merged[provider].levelOverrides) {
151
- merged[provider].levelOverrides = {};
152
- }
153
- }
154
- return merged;
155
- }
156
-
157
- function applyLegacyModelBounds(settings) {
158
- if (!settings.providerSettings) return settings;
159
- const claude = settings.providerSettings.claude || {};
160
- const legacyMaxLevel = mapLegacyModelToLevel(settings.maxModel);
161
- const legacyMinLevel = mapLegacyModelToLevel(settings.minModel);
162
-
163
- if (legacyMaxLevel) {
164
- claude.maxLevel = legacyMaxLevel;
165
- }
166
-
167
- if (legacyMinLevel) {
168
- claude.minLevel = legacyMinLevel;
169
- }
170
-
171
- const minRank = LEVEL_RANKS[claude.minLevel] || LEVEL_RANKS.level1;
172
- const maxRank = LEVEL_RANKS[claude.maxLevel] || LEVEL_RANKS.level3;
173
- const defaultRank = LEVEL_RANKS[claude.defaultLevel] || LEVEL_RANKS.level2;
174
-
175
- if (minRank > maxRank) {
176
- claude.minLevel = 'level1';
177
- claude.maxLevel = 'level3';
178
- } else if (defaultRank < minRank) {
179
- claude.defaultLevel = claude.minLevel;
180
- } else if (defaultRank > maxRank) {
181
- claude.defaultLevel = claude.maxLevel;
182
- }
183
-
184
- settings.providerSettings.claude = claude;
185
- return settings;
186
- }
187
-
188
- function normalizeLoadedSettings(parsed) {
189
- const normalized = { ...parsed };
190
- if (parsed.defaultProvider) {
191
- normalized.defaultProvider = normalizeProviderName(parsed.defaultProvider);
192
- }
193
- if (parsed.providerSettings) {
194
- normalized.providerSettings = normalizeProviderSettings(parsed.providerSettings);
195
- }
196
- return normalized;
197
- }
198
-
199
- /**
200
- * Load settings from disk, merging with defaults
201
- */
202
- function loadSettings() {
203
- const settingsFile = getSettingsFile();
204
- if (!fs.existsSync(settingsFile)) {
205
- return { ...DEFAULT_SETTINGS };
206
- }
207
- try {
208
- const data = fs.readFileSync(settingsFile, 'utf8');
209
- const parsed = normalizeLoadedSettings(JSON.parse(data));
210
- const merged = { ...DEFAULT_SETTINGS, ...parsed };
211
- merged.defaultProvider =
212
- normalizeProviderName(merged.defaultProvider) || DEFAULT_SETTINGS.defaultProvider;
213
- merged.providerSettings = mergeProviderSettings(
214
- DEFAULT_SETTINGS.providerSettings,
215
- parsed.providerSettings
216
- );
217
- return applyLegacyModelBounds(merged);
218
- } catch {
219
- console.error('Warning: Could not load settings, using defaults');
220
- return { ...DEFAULT_SETTINGS };
221
- }
222
- }
223
-
224
- /**
225
- * Save settings to disk
226
- */
227
- function saveSettings(settings) {
228
- const settingsFile = getSettingsFile();
229
- const dir = path.dirname(settingsFile);
230
- if (!fs.existsSync(dir)) {
231
- fs.mkdirSync(dir, { recursive: true });
232
- }
233
- fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2), 'utf8');
234
- }
235
-
236
- /**
237
- * Validate a setting value
238
- * @returns {string|null} Error message if invalid, null if valid
239
- */
240
- function validateSetting(key, value) {
241
- if (!(key in DEFAULT_SETTINGS)) {
242
- return `Unknown setting: ${key}`;
243
- }
244
-
245
- if (key === 'maxModel' && !VALID_MODELS.includes(value)) {
246
- return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}`;
247
- }
248
-
249
- if (key === 'minModel' && value !== null && !VALID_MODELS.includes(value)) {
250
- return `Invalid model: ${value}. Valid models: ${VALID_MODELS.join(', ')}, null`;
251
- }
252
-
253
- if (key === 'logLevel' && !['quiet', 'normal', 'verbose'].includes(value)) {
254
- return `Invalid log level: ${value}. Valid levels: quiet, normal, verbose`;
255
- }
256
-
257
- if (key === 'claudeCommand') {
258
- if (typeof value !== 'string') {
259
- return 'claudeCommand must be a string';
260
- }
261
- if (value.trim().length === 0) {
262
- return 'claudeCommand cannot be empty';
263
- }
264
- }
265
-
266
- if (key === 'defaultProvider') {
267
- const normalized = normalizeProviderName(value);
268
- if (!VALID_PROVIDERS.includes(normalized)) {
269
- return `Invalid provider: ${value}. Valid providers: ${VALID_PROVIDERS.join(', ')}`;
270
- }
271
- }
272
-
273
- if (key === 'providerSettings') {
274
- const normalizedSettings = normalizeProviderSettings(value);
275
- if (typeof value !== 'object' || value === null || Array.isArray(value)) {
276
- return 'providerSettings must be an object';
277
- }
278
- for (const [provider, settings] of Object.entries(normalizedSettings || {})) {
279
- if (!VALID_PROVIDERS.includes(provider)) {
280
- return `Unknown provider in providerSettings: ${provider}`;
281
- }
282
- if (typeof settings !== 'object' || settings === null) {
283
- return `providerSettings.${provider} must be an object`;
284
- }
285
- if (settings.maxLevel && !LEVEL_RANKS[settings.maxLevel]) {
286
- return `Invalid maxLevel for ${provider}: ${settings.maxLevel}`;
287
- }
288
- if (settings.minLevel && !LEVEL_RANKS[settings.minLevel]) {
289
- return `Invalid minLevel for ${provider}: ${settings.minLevel}`;
290
- }
291
- if (settings.defaultLevel && !LEVEL_RANKS[settings.defaultLevel]) {
292
- return `Invalid defaultLevel for ${provider}: ${settings.defaultLevel}`;
293
- }
294
- if (
295
- settings.levelOverrides &&
296
- (typeof settings.levelOverrides !== 'object' || Array.isArray(settings.levelOverrides))
297
- ) {
298
- return `levelOverrides for ${provider} must be an object`;
299
- }
300
- }
301
- }
302
-
303
- if (key === 'dockerMounts') {
304
- return validateMountConfig(value);
305
- }
306
-
307
- if (key === 'dockerEnvPassthrough') {
308
- return validateEnvPassthrough(value);
309
- }
310
-
311
- return null;
312
- }
313
-
314
- /**
315
- * Coerce value to correct type based on default value type
316
- */
317
- function coerceValue(key, value) {
318
- const defaultValue = DEFAULT_SETTINGS[key];
319
-
320
- // Handle null values for minModel
321
- if (key === 'minModel' && (value === 'null' || value === null)) {
322
- return null;
323
- }
324
-
325
- if (typeof defaultValue === 'boolean') {
326
- return value === 'true' || value === '1' || value === 'yes' || value === true;
327
- }
328
-
329
- if (typeof defaultValue === 'number') {
330
- const parsed = parseInt(value);
331
- if (isNaN(parsed)) {
332
- throw new Error(`Invalid number: ${value}`);
333
- }
334
- return parsed;
335
- }
336
-
337
- // Handle array settings (dockerMounts, dockerEnvPassthrough)
338
- if (Array.isArray(defaultValue)) {
339
- if (typeof value === 'string') {
340
- try {
341
- const parsed = JSON.parse(value);
342
- if (!Array.isArray(parsed)) {
343
- throw new Error(`${key} must be an array`);
344
- }
345
- return parsed;
346
- } catch (e) {
347
- if (e instanceof SyntaxError) {
348
- throw new Error(`Invalid JSON for ${key}: ${value}`);
349
- }
350
- throw e;
351
- }
352
- }
353
- return value;
354
- }
355
-
356
- if (key === 'providerSettings') {
357
- if (typeof value === 'string') {
358
- try {
359
- return normalizeProviderSettings(JSON.parse(value));
360
- } catch {
361
- throw new Error(`Invalid JSON for providerSettings: ${value}`);
362
- }
363
- }
364
- return normalizeProviderSettings(value);
365
- }
366
-
367
- if (key === 'defaultProvider') {
368
- return normalizeProviderName(value);
369
- }
370
-
371
- return value;
372
- }
373
-
374
- /**
375
- * Get parsed Claude command from settings/env
376
- * Supports space-separated commands like 'ccr code'
377
- * @returns {{ command: string, args: string[] }}
378
- */
379
- function getClaudeCommand() {
380
- const settings = loadSettings();
381
- const raw = process.env.ZEROSHOT_CLAUDE_COMMAND || settings.claudeCommand || 'claude';
382
- const parts = raw.trim().split(/\s+/);
383
- return {
384
- command: parts[0],
385
- args: parts.slice(1),
386
- };
387
- }
388
-
389
- module.exports = {
390
- loadSettings,
391
- saveSettings,
392
- validateSetting,
393
- coerceValue,
394
- DEFAULT_SETTINGS,
395
- getSettingsFile,
396
- getClaudeCommand,
397
- // Model validation exports
398
- MODEL_HIERARCHY,
399
- VALID_MODELS,
400
- validateModelAgainstMax,
401
- // Backward compatibility: SETTINGS_FILE as getter (reads env var dynamically)
402
- get SETTINGS_FILE() {
403
- return getSettingsFile();
404
- },
405
- };
@@ -1,10 +0,0 @@
1
- /**
2
- * Compatibility wrapper for Claude stream-json parsing.
3
- * Prefer provider-specific parsers in src/providers.
4
- */
5
- const { parseEvent, parseChunk } = require('../src/providers/anthropic/output-parser');
6
-
7
- module.exports = {
8
- parseEvent,
9
- parseChunk,
10
- };
@@ -1,75 +0,0 @@
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
- }