elliot-stack 1.0.23 → 1.0.25
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/README.md +11 -6
- package/bin/install.cjs +158 -25
- package/package.json +1 -1
- package/skills/estack-active-learning-tutor/SKILL.md +32 -1
- package/skills/estack-better-title/SKILL.md +32 -1
- package/skills/estack-chris-voss/SKILL.md +32 -1
- package/skills/estack-customer-discovery/SKILL.md +32 -1
- package/skills/estack-flight-planner/SKILL.md +32 -1
- package/skills/estack-github-issue-tracker/SKILL.md +32 -1
- package/skills/estack-prompt-builder-coach/SKILL.md +32 -1
- package/skills/estack-read-claude-session-history/SKILL.md +32 -1
- package/skills/estack-repo-search/SKILL.md +32 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A curated set of Claude Code skills by Elliot Drel. One command installs them al
|
|
|
11
11
|
npx elliot-stack@latest
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
This installs skills to `~/.agents/` and symlinks them into `~/.claude/skills/`, then registers a `SessionStart` hook so your skills stay up to date automatically.
|
|
14
|
+
This installs skills to `~/.agents/skills/` and symlinks them into `~/.claude/skills/`, then registers a `SessionStart` hook so your skills stay up to date automatically. Any agent that reads from `~/.agents/skills/` (OpenClaw, Codex, ChatGPT, Cursor, and others) will pick them up automatically with no extra config.
|
|
15
15
|
|
|
16
16
|
## Skills
|
|
17
17
|
|
|
@@ -32,12 +32,17 @@ This installs skills to `~/.agents/` and symlinks them into `~/.claude/skills/`,
|
|
|
32
32
|
|
|
33
33
|
Hooks install to `~/.claude/hooks/` and are auto-registered in your `~/.claude/settings.json`.
|
|
34
34
|
|
|
35
|
+
Important: hooks are a Claude Code feature. Skills are stored in `~/.agents/skills/`
|
|
36
|
+
and linked into Claude, but hook scripts and hook registration can only be installed
|
|
37
|
+
in Claude's config (`~/.claude/hooks/` and `~/.claude/settings.json`).
|
|
38
|
+
|
|
35
39
|
## How it works
|
|
36
40
|
|
|
37
|
-
- Skills install to `~/.agents/estack-*/` (symlinked from `~/.claude/skills/estack
|
|
38
|
-
- Hooks install to `~/.claude/hooks/` and are registered in `~/.claude/settings.json`
|
|
39
|
-
- A `SessionStart` hook auto-updates
|
|
41
|
+
- Skills install to `~/.agents/skills/estack-*/` (symlinked from `~/.claude/skills/estack-*/`; auto-detected by any agent that reads `~/.agents/skills/`, including OpenClaw, Codex, ChatGPT, and Cursor)
|
|
42
|
+
- Hooks are Claude Code-only: they install to `~/.claude/hooks/` and are registered in `~/.claude/settings.json`
|
|
43
|
+
- A `SessionStart` hook auto-updates skills each time you open Claude Code
|
|
40
44
|
- If you've made local edits to a skill or hook, the installer detects the conflict and lets you choose: overwrite, skip, or merge
|
|
45
|
+
- Every skill carries its own semver (`version:` in SKILL.md frontmatter; hooks use a `// @version` comment), independent of the package version — update messages show exactly what moved, e.g. `estack-chris-voss (1.0.0 → 1.1.0)`. Under the hood, updates are detected by content hash, so a change can never be missed; a release-time check (`scripts/check-versions.cjs`) guarantees every content change ships with a version bump.
|
|
41
46
|
|
|
42
47
|
## Updating
|
|
43
48
|
|
|
@@ -49,7 +54,7 @@ npx elliot-stack@latest
|
|
|
49
54
|
|
|
50
55
|
## Requirements
|
|
51
56
|
|
|
52
|
-
- [Claude Code](https://claude.ai/code) CLI installed
|
|
57
|
+
- [Claude Code](https://claude.ai/code) CLI installed (other agents that read `~/.agents/skills/` work too)
|
|
53
58
|
- Node.js 18+
|
|
54
59
|
|
|
55
60
|
## Contributing
|
|
@@ -62,7 +67,7 @@ Run the installer straight from your checkout to preview what a real install wou
|
|
|
62
67
|
|
|
63
68
|
```bash
|
|
64
69
|
node bin/install.cjs # dry run — previews changes, writes nothing
|
|
65
|
-
node bin/install.cjs --install # actually sync your local edits to ~/.agents/ + ~/.claude/skills/
|
|
70
|
+
node bin/install.cjs --install # actually sync your local edits to ~/.agents/skills/ + ~/.claude/skills/
|
|
66
71
|
```
|
|
67
72
|
|
|
68
73
|
Run from the repo, the installer **dry-runs by default** so testing never clobbers your live install. Pass `--install` once the preview looks right. (`--dry-run` forces a preview even under `npx`.)
|
package/bin/install.cjs
CHANGED
|
@@ -11,7 +11,8 @@ const readline = require('readline');
|
|
|
11
11
|
const HOME = os.homedir();
|
|
12
12
|
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
13
13
|
const SKILLS_DIR = path.join(CLAUDE_DIR, 'skills');
|
|
14
|
-
const
|
|
14
|
+
const AGENTS_ROOT = path.join(HOME, '.agents');
|
|
15
|
+
const AGENTS_DIR = path.join(AGENTS_ROOT, 'skills');
|
|
15
16
|
const BACKUP_DIR = path.join(HOME, '.estack-backup');
|
|
16
17
|
const CHECKSUMS_FILE = path.join(CLAUDE_DIR, '.estack-checksums.json');
|
|
17
18
|
const SETTINGS_FILE = path.join(CLAUDE_DIR, 'settings.json');
|
|
@@ -61,6 +62,67 @@ const PACKAGE_HOOKS_DIR = path.join(__dirname, '..', 'hooks');
|
|
|
61
62
|
}
|
|
62
63
|
})();
|
|
63
64
|
|
|
65
|
+
// ── Migrate skills from ~/.agents/<name> (v1.0.23 layout) to ~/.agents/skills/ ──
|
|
66
|
+
(function migrateAgentsLayout() {
|
|
67
|
+
let strays;
|
|
68
|
+
try {
|
|
69
|
+
// statSync guards against ~/.agents existing as a plain file
|
|
70
|
+
if (!fs.existsSync(AGENTS_ROOT) || !fs.statSync(AGENTS_ROOT).isDirectory()) return;
|
|
71
|
+
strays = fs.readdirSync(AGENTS_ROOT, { withFileTypes: true })
|
|
72
|
+
.filter((e) => e.isDirectory() && e.name.startsWith('estack-'));
|
|
73
|
+
} catch (_) {
|
|
74
|
+
return; // unreadable — let main() surface a real error if it matters
|
|
75
|
+
}
|
|
76
|
+
if (strays.length === 0) return;
|
|
77
|
+
const silent = process.argv.includes('--silent');
|
|
78
|
+
const isDryRun = process.argv.includes('--dry-run') ||
|
|
79
|
+
(!__dirname.includes('node_modules') && !process.argv.includes('--install'));
|
|
80
|
+
if (isDryRun) {
|
|
81
|
+
if (!silent) {
|
|
82
|
+
process.stderr.write(
|
|
83
|
+
'estack: [dry run] Would move ' + strays.length + ' skill(s) from ~/.agents/ to ~/.agents/skills/\n'
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
process.stderr.write(
|
|
92
|
+
'estack: WARNING — could not create ~/.agents/skills/: ' + err.message + '\n'
|
|
93
|
+
);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (const e of strays) {
|
|
97
|
+
const oldPath = path.join(AGENTS_ROOT, e.name);
|
|
98
|
+
const newPath = path.join(AGENTS_DIR, e.name);
|
|
99
|
+
try {
|
|
100
|
+
if (fs.existsSync(newPath)) {
|
|
101
|
+
// already migrated — drop the stale copy at the old location
|
|
102
|
+
fs.rmSync(oldPath, { recursive: true, force: true });
|
|
103
|
+
} else {
|
|
104
|
+
try {
|
|
105
|
+
fs.renameSync(oldPath, newPath);
|
|
106
|
+
} catch (_) {
|
|
107
|
+
copyDirRaw(oldPath, newPath);
|
|
108
|
+
removeDirRaw(oldPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// re-point the live symlink — the old junction now dangles
|
|
112
|
+
ensureSymlink(newPath, path.join(SKILLS_DIR, e.name));
|
|
113
|
+
} catch (err) {
|
|
114
|
+
process.stderr.write(
|
|
115
|
+
'estack: WARNING — could not migrate ' + e.name + ' to ~/.agents/skills/: ' + err.message + '\n'
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!silent) {
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
'estack: moved ' + strays.length + ' skill(s) from ~/.agents/ to ~/.agents/skills/\n'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
})();
|
|
125
|
+
|
|
64
126
|
function copyDirRaw(src, dest) {
|
|
65
127
|
fs.mkdirSync(dest, { recursive: true });
|
|
66
128
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
@@ -84,6 +146,16 @@ function isSymlink(p) {
|
|
|
84
146
|
try { return fs.lstatSync(p).isSymbolicLink(); } catch (_) { return false; }
|
|
85
147
|
}
|
|
86
148
|
|
|
149
|
+
// True only for a real directory at p — not a symlink to one, not a file.
|
|
150
|
+
function isRealDir(p) {
|
|
151
|
+
try {
|
|
152
|
+
const stat = fs.lstatSync(p);
|
|
153
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
154
|
+
} catch (_) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
87
159
|
// Creates (or updates) a directory symlink at linkPath pointing to target.
|
|
88
160
|
// On Windows uses 'junction' (no elevation required); on Unix uses 'dir'.
|
|
89
161
|
function ensureSymlink(target, linkPath) {
|
|
@@ -92,10 +164,12 @@ function ensureSymlink(target, linkPath) {
|
|
|
92
164
|
if (stat.isSymbolicLink()) {
|
|
93
165
|
if (path.resolve(fs.readlinkSync(linkPath)) === path.resolve(target)) return;
|
|
94
166
|
fs.unlinkSync(linkPath);
|
|
95
|
-
} else
|
|
167
|
+
} else {
|
|
168
|
+
// real dir, plain file, or anything else occupying the link path
|
|
96
169
|
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
97
170
|
}
|
|
98
171
|
} catch (_) {}
|
|
172
|
+
fs.mkdirSync(path.dirname(linkPath), { recursive: true });
|
|
99
173
|
const type = process.platform === 'win32' ? 'junction' : 'dir';
|
|
100
174
|
fs.symlinkSync(target, linkPath, type);
|
|
101
175
|
}
|
|
@@ -148,7 +222,12 @@ function computeFileHash(filePath) {
|
|
|
148
222
|
}
|
|
149
223
|
|
|
150
224
|
function computeSkillHash(skillDir) {
|
|
151
|
-
|
|
225
|
+
// statSync (not lstat) so symlinked dirs hash their contents; plain files → null
|
|
226
|
+
try {
|
|
227
|
+
if (!fs.statSync(skillDir).isDirectory()) return null;
|
|
228
|
+
} catch (_) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
152
231
|
const hash = crypto.createHash('sha256');
|
|
153
232
|
const files = walkDir(skillDir, skillDir);
|
|
154
233
|
for (const relPath of files) {
|
|
@@ -223,19 +302,54 @@ function getSkillDescription(skillDir) {
|
|
|
223
302
|
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
224
303
|
if (!fs.existsSync(skillMd)) return '';
|
|
225
304
|
const content = fs.readFileSync(skillMd, 'utf8');
|
|
226
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
305
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
227
306
|
if (!frontmatterMatch) return '';
|
|
228
307
|
const fm = frontmatterMatch[1];
|
|
229
|
-
const singleLine = fm.match(/^description:\s*(
|
|
230
|
-
if (singleLine) return singleLine[1].trim();
|
|
231
|
-
const multiLine = fm.match(/^description:\s
|
|
308
|
+
const singleLine = fm.match(/^description:\s*(\S.*)$/m);
|
|
309
|
+
if (singleLine && !/^[>|]/.test(singleLine[1])) return singleLine[1].trim();
|
|
310
|
+
const multiLine = fm.match(/^description:\s*[>|][->+]?\r?\n((?:[ \t]+.*\r?\n?)+)/m);
|
|
232
311
|
if (multiLine) {
|
|
233
312
|
return multiLine[1].replace(/\s+/g, ' ').trim();
|
|
234
313
|
}
|
|
235
314
|
return '';
|
|
236
315
|
}
|
|
237
316
|
|
|
238
|
-
//
|
|
317
|
+
// Per-skill version from SKILL.md frontmatter (`version: x.y.z`).
|
|
318
|
+
// Versions are the human-readable label; content hashes remain the
|
|
319
|
+
// update-detection source of truth (scripts/check-versions.cjs keeps them in sync).
|
|
320
|
+
function getSkillVersion(skillDir) {
|
|
321
|
+
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
322
|
+
if (!fs.existsSync(skillMd)) return null;
|
|
323
|
+
const content = fs.readFileSync(skillMd, 'utf8');
|
|
324
|
+
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
325
|
+
if (!frontmatterMatch) return null;
|
|
326
|
+
const m = frontmatterMatch[1].match(/^version:\s*(\S+)\s*$/m);
|
|
327
|
+
return m ? m[1] : null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Version of the currently installed copy of a skill (agents dir, falling
|
|
331
|
+
// back to the legacy skills dir for pre-migration installs).
|
|
332
|
+
function getInstalledSkillVersion(name) {
|
|
333
|
+
const agentsDir = path.join(AGENTS_DIR, name);
|
|
334
|
+
if (fs.existsSync(agentsDir)) return getSkillVersion(agentsDir);
|
|
335
|
+
return getSkillVersion(path.join(SKILLS_DIR, name));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Per-hook version from a `// @version x.y.z` comment near the top.
|
|
339
|
+
function getHookVersion(filePath) {
|
|
340
|
+
if (!fs.existsSync(filePath)) return null;
|
|
341
|
+
const m = fs.readFileSync(filePath, 'utf8').match(/^\/\/ @version\s+(\S+)\s*$/m);
|
|
342
|
+
return m ? m[1] : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// "name (1.0.0 → 1.1.0)" for updates, "name (v1.1.0)" for fresh installs.
|
|
346
|
+
function withVersion(name, oldV, newV) {
|
|
347
|
+
if (oldV && newV && oldV !== newV) return name + ' (' + oldV + ' → ' + newV + ')';
|
|
348
|
+
if (newV) return name + ' (v' + newV + ')';
|
|
349
|
+
return name;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Copies a skill to ~/.agents/skills/<name> and creates/updates the symlink at ~/.claude/skills/<name>.
|
|
239
353
|
// If a real (non-symlink) directory already exists at the skills path, it is removed first.
|
|
240
354
|
function installSkillFiles(name) {
|
|
241
355
|
const agentsSkillDir = path.join(AGENTS_DIR, name);
|
|
@@ -374,7 +488,10 @@ async function main() {
|
|
|
374
488
|
changed = true;
|
|
375
489
|
}
|
|
376
490
|
}
|
|
377
|
-
if (changed && !DRY_RUN)
|
|
491
|
+
if (changed && !DRY_RUN) {
|
|
492
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
493
|
+
fs.writeFileSync(CHECKSUMS_FILE, JSON.stringify(newChecksums0, null, 2));
|
|
494
|
+
}
|
|
378
495
|
}
|
|
379
496
|
|
|
380
497
|
// 1. Scan package skills
|
|
@@ -432,7 +549,7 @@ async function main() {
|
|
|
432
549
|
installedDir = agentsSkillDir;
|
|
433
550
|
} else {
|
|
434
551
|
const legacyDir = path.join(SKILLS_DIR, name);
|
|
435
|
-
if (
|
|
552
|
+
if (isRealDir(legacyDir)) {
|
|
436
553
|
installedDir = legacyDir; // old-style install, will be migrated on next write
|
|
437
554
|
}
|
|
438
555
|
}
|
|
@@ -522,41 +639,51 @@ async function main() {
|
|
|
522
639
|
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
523
640
|
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
524
641
|
const newChecksums = Object.assign({}, storedChecksums);
|
|
525
|
-
const updated = [];
|
|
526
|
-
const mergeNeeded = [];
|
|
642
|
+
const updated = []; // display labels with version transitions
|
|
643
|
+
const mergeNeeded = []; // plain names (used in merge instructions)
|
|
644
|
+
const mergeNeededLabels = []; // display labels with version transitions
|
|
527
645
|
|
|
528
646
|
for (const name of skillNames) {
|
|
647
|
+
const newV = getSkillVersion(path.join(PACKAGE_SKILLS_DIR, name));
|
|
529
648
|
if (modifiedSkills.includes(name)) {
|
|
530
649
|
// Backup local version, install new version
|
|
650
|
+
const oldV = getInstalledSkillVersion(name);
|
|
531
651
|
backupSkill(name);
|
|
532
652
|
installSkillFiles(name);
|
|
533
653
|
newChecksums[name] = packageHashes[name];
|
|
534
654
|
mergeNeeded.push(name);
|
|
655
|
+
mergeNeededLabels.push(withVersion(name, oldV, newV));
|
|
535
656
|
continue;
|
|
536
657
|
}
|
|
537
658
|
if (!needsUpdate.includes(name) && fs.existsSync(path.join(AGENTS_DIR, name))) continue;
|
|
659
|
+
const oldV = getInstalledSkillVersion(name);
|
|
538
660
|
installSkillFiles(name);
|
|
539
661
|
newChecksums[name] = packageHashes[name];
|
|
540
|
-
updated.push(name);
|
|
662
|
+
updated.push(withVersion(name, oldV, newV));
|
|
541
663
|
}
|
|
542
664
|
|
|
543
665
|
// Install hooks
|
|
544
666
|
fs.mkdirSync(HOOKS_DIR, { recursive: true });
|
|
545
667
|
const updatedHooks = [];
|
|
546
668
|
const mergeNeededHooks = [];
|
|
669
|
+
const mergeNeededHookLabels = [];
|
|
547
670
|
|
|
548
671
|
for (const filename of hookFilenames) {
|
|
672
|
+
const newV = getHookVersion(path.join(PACKAGE_HOOKS_DIR, filename));
|
|
549
673
|
if (modifiedHooks.includes(filename)) {
|
|
674
|
+
const oldV = getHookVersion(path.join(HOOKS_DIR, filename));
|
|
550
675
|
backupHook(filename);
|
|
551
676
|
fs.copyFileSync(path.join(PACKAGE_HOOKS_DIR, filename), path.join(HOOKS_DIR, filename));
|
|
552
677
|
newChecksums['hook:' + filename] = packageHookHashes[filename];
|
|
553
678
|
mergeNeededHooks.push(filename);
|
|
679
|
+
mergeNeededHookLabels.push(withVersion(filename, oldV, newV));
|
|
554
680
|
continue;
|
|
555
681
|
}
|
|
556
682
|
if (!hooksNeedingUpdate.includes(filename) && fs.existsSync(path.join(HOOKS_DIR, filename))) continue;
|
|
683
|
+
const oldV = getHookVersion(path.join(HOOKS_DIR, filename));
|
|
557
684
|
fs.copyFileSync(path.join(PACKAGE_HOOKS_DIR, filename), path.join(HOOKS_DIR, filename));
|
|
558
685
|
newChecksums['hook:' + filename] = packageHookHashes[filename];
|
|
559
|
-
updatedHooks.push(filename);
|
|
686
|
+
updatedHooks.push(withVersion(filename, oldV, newV));
|
|
560
687
|
}
|
|
561
688
|
|
|
562
689
|
setupRepoSearchNudgeHook();
|
|
@@ -578,7 +705,7 @@ async function main() {
|
|
|
578
705
|
if (mergeNeeded.length > 0) {
|
|
579
706
|
const backupPath = BACKUP_DIR.replace(HOME, '~');
|
|
580
707
|
msgParts.push(
|
|
581
|
-
'estack: updated ' +
|
|
708
|
+
'estack: updated ' + mergeNeededLabels.join(', ') +
|
|
582
709
|
' (local changes backed up to ' + backupPath + ')'
|
|
583
710
|
);
|
|
584
711
|
output.additionalContext =
|
|
@@ -595,7 +722,7 @@ async function main() {
|
|
|
595
722
|
if (mergeNeededHooks.length > 0) {
|
|
596
723
|
const backupPath = BACKUP_DIR.replace(HOME, '~');
|
|
597
724
|
msgParts.push(
|
|
598
|
-
'estack: updated hooks ' +
|
|
725
|
+
'estack: updated hooks ' + mergeNeededHookLabels.join(', ') +
|
|
599
726
|
' (local changes backed up to ' + backupPath + '/hooks/)'
|
|
600
727
|
);
|
|
601
728
|
const existingContext = output.additionalContext ? output.additionalContext + ' ' : '';
|
|
@@ -694,16 +821,18 @@ async function main() {
|
|
|
694
821
|
if (DRY_RUN) console.log(' [dry run] Up to date (no change): ' + name);
|
|
695
822
|
continue;
|
|
696
823
|
}
|
|
697
|
-
const skillsLegacyDir = path.join(SKILLS_DIR, name);
|
|
698
824
|
const isUpdate = fs.existsSync(path.join(AGENTS_DIR, name)) ||
|
|
699
|
-
(
|
|
825
|
+
isRealDir(path.join(SKILLS_DIR, name));
|
|
826
|
+
const label = withVersion(name,
|
|
827
|
+
isUpdate ? getInstalledSkillVersion(name) : null,
|
|
828
|
+
getSkillVersion(path.join(PACKAGE_SKILLS_DIR, name)));
|
|
700
829
|
if (!DRY_RUN) installSkillFiles(name);
|
|
701
830
|
newChecksums[name] = packageHashes[name];
|
|
702
831
|
installedCount++;
|
|
703
832
|
if (DRY_RUN) {
|
|
704
|
-
console.log(' [dry run] Would ' + (isUpdate ? 'update ' : 'install ') +
|
|
833
|
+
console.log(' [dry run] Would ' + (isUpdate ? 'update ' : 'install ') + label);
|
|
705
834
|
} else {
|
|
706
|
-
console.log(' Installed ' +
|
|
835
|
+
console.log(' Installed ' + label);
|
|
707
836
|
}
|
|
708
837
|
}
|
|
709
838
|
|
|
@@ -732,13 +861,16 @@ async function main() {
|
|
|
732
861
|
continue;
|
|
733
862
|
}
|
|
734
863
|
const isHookUpdate = fs.existsSync(path.join(HOOKS_DIR, filename));
|
|
864
|
+
const hookLabel = withVersion(filename,
|
|
865
|
+
isHookUpdate ? getHookVersion(path.join(HOOKS_DIR, filename)) : null,
|
|
866
|
+
getHookVersion(path.join(PACKAGE_HOOKS_DIR, filename)));
|
|
735
867
|
if (!DRY_RUN) fs.copyFileSync(path.join(PACKAGE_HOOKS_DIR, filename), path.join(HOOKS_DIR, filename));
|
|
736
868
|
newChecksums['hook:' + filename] = packageHookHashes[filename];
|
|
737
869
|
installedHookCount++;
|
|
738
870
|
if (DRY_RUN) {
|
|
739
|
-
console.log(' [dry run] Would ' + (isHookUpdate ? 'update hook ' : 'install hook ') +
|
|
871
|
+
console.log(' [dry run] Would ' + (isHookUpdate ? 'update hook ' : 'install hook ') + hookLabel);
|
|
740
872
|
} else {
|
|
741
|
-
console.log(' Installed hook ' +
|
|
873
|
+
console.log(' Installed hook ' + hookLabel);
|
|
742
874
|
}
|
|
743
875
|
}
|
|
744
876
|
|
|
@@ -753,13 +885,13 @@ async function main() {
|
|
|
753
885
|
// 11. Summary output
|
|
754
886
|
if (DRY_RUN) {
|
|
755
887
|
console.log('\n[dry run] No files were changed. Run with --install to apply.\n');
|
|
756
|
-
console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' would be installed/updated in ~/.agents/ (linked from ~/.claude/skills/)');
|
|
888
|
+
console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' would be installed/updated in ~/.agents/skills/ (linked from ~/.claude/skills/; auto-detected by any agent that reads ~/.agents/skills/)');
|
|
757
889
|
if (installedHookCount > 0) {
|
|
758
890
|
console.log(' ' + installedHookCount + ' hook' + (installedHookCount !== 1 ? 's' : '') + ' would be installed/updated in ~/.claude/hooks/');
|
|
759
891
|
}
|
|
760
892
|
} else {
|
|
761
893
|
console.log('\nestack installed successfully!\n');
|
|
762
|
-
console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' installed to ~/.agents/ (symlinked from ~/.claude/skills/)');
|
|
894
|
+
console.log(' ' + installedCount + ' skill' + (installedCount !== 1 ? 's' : '') + ' installed to ~/.agents/skills/ (symlinked from ~/.claude/skills/; auto-detected by any agent that reads ~/.agents/skills/)');
|
|
763
895
|
if (installedHookCount > 0) {
|
|
764
896
|
console.log(' ' + installedHookCount + ' hook' + (installedHookCount !== 1 ? 's' : '') + ' installed to ~/.claude/hooks/');
|
|
765
897
|
}
|
|
@@ -769,7 +901,8 @@ async function main() {
|
|
|
769
901
|
|
|
770
902
|
for (const name of skillNames) {
|
|
771
903
|
const desc = getSkillDescription(path.join(PACKAGE_SKILLS_DIR, name));
|
|
772
|
-
|
|
904
|
+
const ver = getSkillVersion(path.join(PACKAGE_SKILLS_DIR, name));
|
|
905
|
+
console.log(' /' + name + (ver ? ' v' + ver : '') + (desc ? ' — ' + desc : ''));
|
|
773
906
|
}
|
|
774
907
|
|
|
775
908
|
if (mergedSkills.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-active-learning-tutor
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (active-learning-tutor) Tutors a student through exam preparation using active learning — questioning, gap diagnosis, and concept mastery tracking. Use when the student says they want to study, learn, prep for an exam, be quizzed on a chapter, work through a practice test together, or be taught a topic conceptually rather than lectured.
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
---
|
|
@@ -401,3 +401,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
401
401
|
Share the printed URL with the user and offer to open it in their browser.
|
|
402
402
|
|
|
403
403
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Skill Feedback
|
|
408
|
+
|
|
409
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
410
|
+
|
|
411
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
gh issue create \
|
|
415
|
+
--repo ElliotDrel/e-stack \
|
|
416
|
+
--title "estack-active-learning-tutor: <concise summary>" \
|
|
417
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
421
|
+
|
|
422
|
+
```bash
|
|
423
|
+
python3 -c "
|
|
424
|
+
import urllib.parse
|
|
425
|
+
title = 'estack-active-learning-tutor: <concise summary>'
|
|
426
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
427
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
428
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
429
|
+
"
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
433
|
+
|
|
434
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-better-title
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (better-title) Suggest better chat session titles and rename the session
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash, AskUserQuestion
|
|
@@ -155,3 +155,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
155
155
|
Share the printed URL with the user and offer to open it in their browser.
|
|
156
156
|
|
|
157
157
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Skill Feedback
|
|
162
|
+
|
|
163
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
164
|
+
|
|
165
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
gh issue create \
|
|
169
|
+
--repo ElliotDrel/e-stack \
|
|
170
|
+
--title "estack-better-title: <concise summary>" \
|
|
171
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python3 -c "
|
|
178
|
+
import urllib.parse
|
|
179
|
+
title = 'estack-better-title: <concise summary>'
|
|
180
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
181
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
182
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
183
|
+
"
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
187
|
+
|
|
188
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-chris-voss
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: >
|
|
5
5
|
(chris-voss) Applies Chris Voss negotiation principles from *Never Split the Difference* to any situation
|
|
6
6
|
where understanding human psychology, persuasion, or influence would improve the output. Use
|
|
@@ -171,3 +171,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
171
171
|
Share the printed URL with the user and offer to open it in their browser.
|
|
172
172
|
|
|
173
173
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Skill Feedback
|
|
178
|
+
|
|
179
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
180
|
+
|
|
181
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
gh issue create \
|
|
185
|
+
--repo ElliotDrel/e-stack \
|
|
186
|
+
--title "estack-chris-voss: <concise summary>" \
|
|
187
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
python3 -c "
|
|
194
|
+
import urllib.parse
|
|
195
|
+
title = 'estack-chris-voss: <concise summary>'
|
|
196
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
197
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
198
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
199
|
+
"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
203
|
+
|
|
204
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-customer-discovery
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (customer-discovery) Guide users through customer discovery — validating business ideas, identifying target customers, crafting outreach, preparing interview questions, and analyzing interview results. Use this skill whenever the user mentions customer discovery, customer interviews, validating an idea, market research, finding product-market fit, talking to customers, outreach messages, interview guides, or analyzing customer feedback. Also use when someone says they have a business idea and want to test it, or when they're preparing to talk to potential customers.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -151,3 +151,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
151
151
|
Share the printed URL with the user and offer to open it in their browser.
|
|
152
152
|
|
|
153
153
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Skill Feedback
|
|
158
|
+
|
|
159
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
160
|
+
|
|
161
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
gh issue create \
|
|
165
|
+
--repo ElliotDrel/e-stack \
|
|
166
|
+
--title "estack-customer-discovery: <concise summary>" \
|
|
167
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
python3 -c "
|
|
174
|
+
import urllib.parse
|
|
175
|
+
title = 'estack-customer-discovery: <concise summary>'
|
|
176
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
177
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
178
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
179
|
+
"
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
183
|
+
|
|
184
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-flight-planner
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (flight-planner) Find and rank flights between any two airports with config-driven preferences (budget, airlines, nonstop, time-of-day) and optional ground-shuttle pairing. Uses SerpAPI Google Flights (or WebSearch fallback). Saves preferences to `~/.flight-planner/config.json` and logs every search.
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
---
|
|
@@ -428,3 +428,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
428
428
|
Share the printed URL with the user and offer to open it in their browser.
|
|
429
429
|
|
|
430
430
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## Skill Feedback
|
|
435
|
+
|
|
436
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
437
|
+
|
|
438
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
gh issue create \
|
|
442
|
+
--repo ElliotDrel/e-stack \
|
|
443
|
+
--title "estack-flight-planner: <concise summary>" \
|
|
444
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
python3 -c "
|
|
451
|
+
import urllib.parse
|
|
452
|
+
title = 'estack-flight-planner: <concise summary>'
|
|
453
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
454
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
455
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
456
|
+
"
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
460
|
+
|
|
461
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-github-issue-tracker
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: >
|
|
5
5
|
(github-issue-tracker) GitHub issue tracker management. Checks all open issues the user is involved in,
|
|
6
6
|
finds related/duplicate issues, reports what changed, and recommends next steps.
|
|
@@ -402,3 +402,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
402
402
|
Share the printed URL with the user and offer to open it in their browser.
|
|
403
403
|
|
|
404
404
|
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Skill Feedback
|
|
409
|
+
|
|
410
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
411
|
+
|
|
412
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
gh issue create \
|
|
416
|
+
--repo ElliotDrel/e-stack \
|
|
417
|
+
--title "estack-github-issue-tracker: <concise summary>" \
|
|
418
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
python3 -c "
|
|
425
|
+
import urllib.parse
|
|
426
|
+
title = 'estack-github-issue-tracker: <concise summary>'
|
|
427
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
428
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
429
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
430
|
+
"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
434
|
+
|
|
435
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-prompt-builder-coach
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (prompt-builder-coach) Use whenever you or the user need to write, sharpen, audit, or scope a prompt or work request for an AI agent or model. This is a four-part kit covering shaping a fuzzy idea into a decided goal, building a prompt from scratch, auditing a draft request that feels vague, and defining what "done" looks like when the task is fuzzy. Trigger when the user says "help me write a prompt", "build me a prompt", "audit this prompt", "make this request better", "why is the AI giving me generic output", "I don't know what I want", "I have a rough idea", "what should done look like", or when handing a task to another agent and wanting it to land. Use it even when the user did not say the word "prompt" but is clearly trying to get an AI to do consequential work. Do not use for quick factual lookups or for executing an already well-defined task.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -79,3 +79,34 @@ Every finished prompt or brief gets saved to a markdown file with a descriptive
|
|
|
79
79
|
- Do not run a part from memory. Always read its file on entry, per Rule 1.
|
|
80
80
|
- Do not over-apply the kit. A quick ask gets a quick prompt, not a six-field brief.
|
|
81
81
|
</guardrails>
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Skill Feedback
|
|
86
|
+
|
|
87
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
88
|
+
|
|
89
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
gh issue create \
|
|
93
|
+
--repo ElliotDrel/e-stack \
|
|
94
|
+
--title "estack-prompt-builder-coach: <concise summary>" \
|
|
95
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python3 -c "
|
|
102
|
+
import urllib.parse
|
|
103
|
+
title = 'estack-prompt-builder-coach: <concise summary>'
|
|
104
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
105
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
106
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
107
|
+
"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
111
|
+
|
|
112
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-read-claude-session-history
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: (read-claude-session-history) Invoke for ANY task involving Claude Code session history, transcripts, or .jsonl files — this is the only way to read, parse, or search them; do not attempt to use Bash or Read on .jsonl directly. Use for: recovering context after /compact ("what were we doing before compact"), advisor response retrieval ("what did the advisor say"), subagent output collection ("get all subagent finals"), cross-project session search by keyword, session listing and triage, UUID and title lookup, resume-command generation, file-edit and tool-call forensics, session diff between two sessions or subagents, weekly work journal, day timeline of activity blocks and idle gaps, engagement/attention-time accounting (active vs elapsed time, break detection, parallel-chat-safe totals), recovering from .claude-backups after data loss, session count queries, and reading the last agent message before a crash or interrupt. Trigger phrases: "session history", "before compact", "what did claude do", "what did I work on", "search my sessions", "find that session", "what did the advisor say", "what did the agent edit", "from the backup", "list my sessions", "subagent outputs", "session journal", "resume previous", "which files did claude touch", "go back and look", "what did I do yesterday", "where did my day go", "timeline of my day", "how much time on", "how long did that actually take", "how much did I actually work", "active time", "time I spent".
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -202,3 +202,34 @@ See `references/recipes.md` for fuller multi-step workflows.
|
|
|
202
202
|
## When the modes return empty
|
|
203
203
|
|
|
204
204
|
If a mode returns empty/unexpected output, run `--mode debug` first. It prints the entry-type distribution, content-block types, and probes for advisor + compact markers — useful when the transcript schema has drifted or when a session was truncated.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Skill Feedback
|
|
209
|
+
|
|
210
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
211
|
+
|
|
212
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
gh issue create \
|
|
216
|
+
--repo ElliotDrel/e-stack \
|
|
217
|
+
--title "estack-read-claude-session-history: <concise summary>" \
|
|
218
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
python3 -c "
|
|
225
|
+
import urllib.parse
|
|
226
|
+
title = 'estack-read-claude-session-history: <concise summary>'
|
|
227
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
228
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
229
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
230
|
+
"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
234
|
+
|
|
235
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: estack-repo-search
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.1
|
|
4
4
|
description: >-
|
|
5
5
|
(repo-search) Clone and search external GitHub repositories to answer questions about their
|
|
6
6
|
code. Use this skill whenever the user references a repo you don't have local
|
|
@@ -91,3 +91,34 @@ print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quo
|
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Share the printed URL with the user. They click it, review the pre-filled title and body, then click **Submit new issue**.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Skill Feedback
|
|
98
|
+
|
|
99
|
+
If the user shares feedback about this skill — a bug, something confusing, a missing feature, or a suggestion — ask them to describe it in a bit more detail (what they expected, what happened, and any relevant context). Then file the issue using whichever method is available:
|
|
100
|
+
|
|
101
|
+
**If `gh` is installed** (`gh --version` succeeds), create the issue directly:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
gh issue create \
|
|
105
|
+
--repo ElliotDrel/e-stack \
|
|
106
|
+
--title "estack-repo-search: <concise summary>" \
|
|
107
|
+
--body "<description from user feedback — expected vs. actual behavior and context>"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**If `gh` is not installed**, build a pre-filled URL:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python3 -c "
|
|
114
|
+
import urllib.parse
|
|
115
|
+
title = 'estack-repo-search: <concise summary>'
|
|
116
|
+
body = '<description from user feedback — expected vs. actual behavior and context>'
|
|
117
|
+
base = 'https://github.com/ElliotDrel/e-stack/issues/new'
|
|
118
|
+
print(base + '?title=' + urllib.parse.quote(title) + '&body=' + urllib.parse.quote(body))
|
|
119
|
+
"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Share the printed URL with the user and offer to open it in their browser.
|
|
123
|
+
|
|
124
|
+
They can also click it directly, review the pre-filled title and body, and click **Submit new issue**.
|