get-shit-done-cc 1.6.3 → 1.6.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/bin/install.js +161 -7
- package/package.json +1 -1
package/bin/install.js
CHANGED
|
@@ -151,6 +151,96 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Clean up orphaned files from previous GSD versions
|
|
156
|
+
*/
|
|
157
|
+
function cleanupOrphanedFiles(claudeDir) {
|
|
158
|
+
const orphanedFiles = [
|
|
159
|
+
'hooks/gsd-notify.sh', // Removed in v1.6.x
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
for (const relPath of orphanedFiles) {
|
|
163
|
+
const fullPath = path.join(claudeDir, relPath);
|
|
164
|
+
if (fs.existsSync(fullPath)) {
|
|
165
|
+
fs.unlinkSync(fullPath);
|
|
166
|
+
console.log(` ${green}✓${reset} Removed orphaned ${relPath}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Clean up orphaned hook registrations from settings.json
|
|
173
|
+
*/
|
|
174
|
+
function cleanupOrphanedHooks(settings) {
|
|
175
|
+
const orphanedHookPatterns = [
|
|
176
|
+
'gsd-notify.sh', // Removed in v1.6.x
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
let cleaned = false;
|
|
180
|
+
|
|
181
|
+
// Check all hook event types (Stop, SessionStart, etc.)
|
|
182
|
+
if (settings.hooks) {
|
|
183
|
+
for (const eventType of Object.keys(settings.hooks)) {
|
|
184
|
+
const hookEntries = settings.hooks[eventType];
|
|
185
|
+
if (Array.isArray(hookEntries)) {
|
|
186
|
+
// Filter out entries that contain orphaned hooks
|
|
187
|
+
const filtered = hookEntries.filter(entry => {
|
|
188
|
+
if (entry.hooks && Array.isArray(entry.hooks)) {
|
|
189
|
+
// Check if any hook in this entry matches orphaned patterns
|
|
190
|
+
const hasOrphaned = entry.hooks.some(h =>
|
|
191
|
+
h.command && orphanedHookPatterns.some(pattern => h.command.includes(pattern))
|
|
192
|
+
);
|
|
193
|
+
if (hasOrphaned) {
|
|
194
|
+
cleaned = true;
|
|
195
|
+
return false; // Remove this entry
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return true; // Keep this entry
|
|
199
|
+
});
|
|
200
|
+
settings.hooks[eventType] = filtered;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (cleaned) {
|
|
206
|
+
console.log(` ${green}✓${reset} Removed orphaned hook registrations`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return settings;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Verify a directory exists and contains files
|
|
214
|
+
*/
|
|
215
|
+
function verifyInstalled(dirPath, description) {
|
|
216
|
+
if (!fs.existsSync(dirPath)) {
|
|
217
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: directory not created`);
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
const entries = fs.readdirSync(dirPath);
|
|
222
|
+
if (entries.length === 0) {
|
|
223
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: directory is empty`);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Verify a file exists
|
|
235
|
+
*/
|
|
236
|
+
function verifyFileInstalled(filePath, description) {
|
|
237
|
+
if (!fs.existsSync(filePath)) {
|
|
238
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: file not created`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
154
244
|
/**
|
|
155
245
|
* Install to the specified directory
|
|
156
246
|
*/
|
|
@@ -175,6 +265,12 @@ function install(isGlobal) {
|
|
|
175
265
|
|
|
176
266
|
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
177
267
|
|
|
268
|
+
// Track installation failures
|
|
269
|
+
const failures = [];
|
|
270
|
+
|
|
271
|
+
// Clean up orphaned files from previous versions
|
|
272
|
+
cleanupOrphanedFiles(claudeDir);
|
|
273
|
+
|
|
178
274
|
// Create commands directory
|
|
179
275
|
const commandsDir = path.join(claudeDir, 'commands');
|
|
180
276
|
fs.mkdirSync(commandsDir, { recursive: true });
|
|
@@ -183,13 +279,21 @@ function install(isGlobal) {
|
|
|
183
279
|
const gsdSrc = path.join(src, 'commands', 'gsd');
|
|
184
280
|
const gsdDest = path.join(commandsDir, 'gsd');
|
|
185
281
|
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix);
|
|
186
|
-
|
|
282
|
+
if (verifyInstalled(gsdDest, 'commands/gsd')) {
|
|
283
|
+
console.log(` ${green}✓${reset} Installed commands/gsd`);
|
|
284
|
+
} else {
|
|
285
|
+
failures.push('commands/gsd');
|
|
286
|
+
}
|
|
187
287
|
|
|
188
288
|
// Copy get-shit-done skill with path replacement
|
|
189
289
|
const skillSrc = path.join(src, 'get-shit-done');
|
|
190
290
|
const skillDest = path.join(claudeDir, 'get-shit-done');
|
|
191
291
|
copyWithPathReplacement(skillSrc, skillDest, pathPrefix);
|
|
192
|
-
|
|
292
|
+
if (verifyInstalled(skillDest, 'get-shit-done')) {
|
|
293
|
+
console.log(` ${green}✓${reset} Installed get-shit-done`);
|
|
294
|
+
} else {
|
|
295
|
+
failures.push('get-shit-done');
|
|
296
|
+
}
|
|
193
297
|
|
|
194
298
|
// Copy agents to ~/.claude/agents (subagents must be at root level)
|
|
195
299
|
// Only delete gsd-*.md files to preserve user's custom agents
|
|
@@ -216,7 +320,11 @@ function install(isGlobal) {
|
|
|
216
320
|
fs.writeFileSync(path.join(agentsDest, entry.name), content);
|
|
217
321
|
}
|
|
218
322
|
}
|
|
219
|
-
|
|
323
|
+
if (verifyInstalled(agentsDest, 'agents')) {
|
|
324
|
+
console.log(` ${green}✓${reset} Installed agents`);
|
|
325
|
+
} else {
|
|
326
|
+
failures.push('agents');
|
|
327
|
+
}
|
|
220
328
|
}
|
|
221
329
|
|
|
222
330
|
// Copy CHANGELOG.md
|
|
@@ -224,13 +332,21 @@ function install(isGlobal) {
|
|
|
224
332
|
const changelogDest = path.join(claudeDir, 'get-shit-done', 'CHANGELOG.md');
|
|
225
333
|
if (fs.existsSync(changelogSrc)) {
|
|
226
334
|
fs.copyFileSync(changelogSrc, changelogDest);
|
|
227
|
-
|
|
335
|
+
if (verifyFileInstalled(changelogDest, 'CHANGELOG.md')) {
|
|
336
|
+
console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
|
|
337
|
+
} else {
|
|
338
|
+
failures.push('CHANGELOG.md');
|
|
339
|
+
}
|
|
228
340
|
}
|
|
229
341
|
|
|
230
342
|
// Write VERSION file for whats-new command
|
|
231
343
|
const versionDest = path.join(claudeDir, 'get-shit-done', 'VERSION');
|
|
232
344
|
fs.writeFileSync(versionDest, pkg.version);
|
|
233
|
-
|
|
345
|
+
if (verifyFileInstalled(versionDest, 'VERSION')) {
|
|
346
|
+
console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
|
|
347
|
+
} else {
|
|
348
|
+
failures.push('VERSION');
|
|
349
|
+
}
|
|
234
350
|
|
|
235
351
|
// Copy hooks
|
|
236
352
|
const hooksSrc = path.join(src, 'hooks');
|
|
@@ -243,12 +359,23 @@ function install(isGlobal) {
|
|
|
243
359
|
const destFile = path.join(hooksDest, entry);
|
|
244
360
|
fs.copyFileSync(srcFile, destFile);
|
|
245
361
|
}
|
|
246
|
-
|
|
362
|
+
if (verifyInstalled(hooksDest, 'hooks')) {
|
|
363
|
+
console.log(` ${green}✓${reset} Installed hooks`);
|
|
364
|
+
} else {
|
|
365
|
+
failures.push('hooks');
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// If critical components failed, exit with error
|
|
370
|
+
if (failures.length > 0) {
|
|
371
|
+
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
|
372
|
+
console.error(` Try running directly: node ~/.npm/_npx/*/node_modules/get-shit-done-cc/bin/install.js --global\n`);
|
|
373
|
+
process.exit(1);
|
|
247
374
|
}
|
|
248
375
|
|
|
249
376
|
// Configure statusline and hooks in settings.json
|
|
250
377
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
251
|
-
const settings = readSettings(settingsPath);
|
|
378
|
+
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
|
252
379
|
const statuslineCommand = isGlobal
|
|
253
380
|
? 'node "$HOME/.claude/hooks/statusline.js"'
|
|
254
381
|
: 'node .claude/hooks/statusline.js';
|
|
@@ -364,11 +491,37 @@ function handleStatusline(settings, isInteractive, callback) {
|
|
|
364
491
|
* Prompt for install location
|
|
365
492
|
*/
|
|
366
493
|
function promptLocation() {
|
|
494
|
+
// Check if stdin is a TTY - if not, fall back to global install
|
|
495
|
+
// This handles npx execution in environments like WSL2 where stdin may not be properly connected
|
|
496
|
+
if (!process.stdin.isTTY) {
|
|
497
|
+
console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
|
|
498
|
+
const { settingsPath, settings, statuslineCommand } = install(true);
|
|
499
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
500
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
367
505
|
const rl = readline.createInterface({
|
|
368
506
|
input: process.stdin,
|
|
369
507
|
output: process.stdout
|
|
370
508
|
});
|
|
371
509
|
|
|
510
|
+
// Track whether we've processed the answer to prevent double-execution
|
|
511
|
+
let answered = false;
|
|
512
|
+
|
|
513
|
+
// Handle readline close event to detect premature stdin closure
|
|
514
|
+
rl.on('close', () => {
|
|
515
|
+
if (!answered) {
|
|
516
|
+
answered = true;
|
|
517
|
+
console.log(`\n ${yellow}Input stream closed, defaulting to global install${reset}\n`);
|
|
518
|
+
const { settingsPath, settings, statuslineCommand } = install(true);
|
|
519
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
520
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
372
525
|
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
|
373
526
|
const globalPath = configDir || path.join(os.homedir(), '.claude');
|
|
374
527
|
const globalLabel = globalPath.replace(os.homedir(), '~');
|
|
@@ -380,6 +533,7 @@ function promptLocation() {
|
|
|
380
533
|
`);
|
|
381
534
|
|
|
382
535
|
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
536
|
+
answered = true;
|
|
383
537
|
rl.close();
|
|
384
538
|
const choice = answer.trim() || '1';
|
|
385
539
|
const isGlobal = choice !== '2';
|
package/package.json
CHANGED