github-issue-builder 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -16,6 +16,7 @@ Add `--dry-run` to see what would change.
16
16
 
17
17
  **Updating.** Run `npx github-issue-builder@latest install` again. The installer:
18
18
  - backs up any existing copy to `~/.github-issue-builder-backups/` and replaces it, or says "already up to date";
19
+ - keeps a link between the two install folders (for example `~/.claude/skills/github-issue-builder` pointing at `~/.agents/skills/github-issue-builder`) and updates the folder it points to;
19
20
  - removes old copies from `~/.codex/skills` (and from `$CODEX_HOME/skills`), so Codex doesn't load the skill twice. Pass `--keep-legacy` to leave them;
20
21
  - warns if your **claude.ai account** also has this skill (synced into `~/.claude/skills/synced/`). Only claude.ai can update that copy, so replace or delete it in claude.ai → Settings → Capabilities → Skills.
21
22
 
@@ -1,217 +1,257 @@
1
- #!/usr/bin/env node
2
- // github-issue-builder: installs the github-issue-builder skill for Claude Code and Codex.
3
- //
4
- // npx github-issue-builder install [--scope user|project] [--project DIR] [--only claude|codex] [--dry-run] [--home DIR]
5
- // [--keep-legacy] [--codex-home DIR]
6
- // npx github-issue-builder verify [--scope user|project] [--project DIR] [--only claude|codex] [--home DIR]
7
- // npx github-issue-builder uninstall [--scope user|project] [--project DIR] [--only claude|codex] [--dry-run] [--home DIR]
8
- //
9
- // Claude Code reads skills from .claude/skills and Codex from .agents/skills. The skill is identical for both,
10
- // so the same folder is copied to each. An existing copy is moved to a timestamped backup before it is replaced.
11
- // Old copies in <codex home>/skills are backed up and removed too (--keep-legacy skips that), and copies synced
12
- // from a claude.ai account are reported, since only claude.ai can update them.
13
- import fs from 'node:fs';
14
- import os from 'node:os';
15
- import path from 'node:path';
16
- import crypto from 'node:crypto';
17
- import { fileURLToPath } from 'node:url';
18
-
19
- const NAME = 'github-issue-builder';
20
- const BUNDLE = path.dirname(fileURLToPath(import.meta.url));
21
- const SRC = path.join(BUNDLE, 'payload', NAME);
22
- const argv = process.argv.slice(2);
23
- const command = argv[0] === undefined || argv[0].startsWith('--') ? 'install' : argv[0];
24
- const hasFlag = (name) => argv.includes(name);
25
- const optionValue = (name) => {
26
- const i = argv.indexOf(name);
27
- return i === -1 ? undefined : argv[i + 1];
28
- };
29
-
30
- const DRY = hasFlag('--dry-run');
31
- const ONLY = optionValue('--only');
32
- const SCOPE = optionValue('--scope') ?? 'user';
33
- const HOME = path.resolve(optionValue('--home') ?? os.homedir());
34
- const ROOT = SCOPE === 'project' ? path.resolve(optionValue('--project') ?? process.cwd()) : HOME;
35
- const BACKUP_DIR = path.join(HOME, `.${NAME}-backups`, new Date().toISOString().replace(/[:.]/g, '-'));
36
-
37
- if (!['user', 'project'].includes(SCOPE)) fail(`--scope must be user or project, got ${SCOPE}`);
38
- if (ONLY && !['claude', 'codex'].includes(ONLY)) fail(`--only must be claude or codex, got ${ONLY}`);
39
-
40
- const KEEP_LEGACY = hasFlag('--keep-legacy');
41
-
42
- const TARGETS = [
43
- { tool: 'Claude Code', key: 'claude', dir: path.join(ROOT, '.claude', 'skills', NAME) },
44
- { tool: 'Codex', key: 'codex', dir: path.join(ROOT, '.agents', 'skills', NAME) },
45
- ].filter((t) => !ONLY || t.key === ONLY);
46
-
47
- // Older Codex versions (and some setups) read skills from <codex home>/skills. A copy left there makes Codex load
48
- // the skill twice, old and new, so install and uninstall move it to the backup folder. Orca and other launchers
49
- // point CODEX_HOME somewhere else, so that home is checked too.
50
- function legacyCodexCopies() {
51
- if (ONLY === 'claude') return [];
52
- // With --home (e.g. a test sandbox) the real CODEX_HOME is ignored so nothing outside that home is touched.
53
- const codexHome = optionValue('--codex-home') ?? (optionValue('--home') ? undefined : process.env.CODEX_HOME);
54
- const homes = SCOPE === 'project'
55
- ? [path.join(ROOT, '.codex')]
56
- : [path.join(HOME, '.codex'), codexHome].filter(Boolean);
57
- const seen = new Set();
58
- return homes
59
- .map((h) => path.join(path.resolve(h), 'skills', NAME))
60
- .filter((d) => !seen.has(d.toLowerCase()) && seen.add(d.toLowerCase()) && exists(d));
61
- }
62
-
63
- // Skills added to a claude.ai account are synced into ~/.claude/skills/synced/<account>/<name>. Claude Code loads
64
- // them next to local skills, and the sync restores anything deleted locally, so they can only be reported.
65
- function syncedClaudeCopies() {
66
- if (ONLY === 'codex' || SCOPE === 'project') return [];
67
- const root = path.join(HOME, '.claude', 'skills', 'synced');
68
- if (!exists(root)) return [];
69
- return fs.readdirSync(root, { withFileTypes: true })
70
- .filter((e) => e.isDirectory())
71
- .map((e) => path.join(root, e.name, NAME))
72
- .filter((d) => exists(path.join(d, 'SKILL.md')));
73
- }
74
-
75
- function warnSynced(copies) {
76
- for (const d of copies) {
77
- console.log(`! claude.ai account copy found: ${d}`);
78
- console.log(' Claude Code loads it alongside this install and the claude.ai sync restores it if deleted here.');
79
- console.log(` To finish updating, open claude.ai > Settings > Capabilities > Skills and replace or delete "${NAME}".`);
80
- }
81
- }
82
-
83
- function fail(msg) {
84
- console.error(`${NAME}: ${msg}`);
85
- process.exit(1);
86
- }
87
-
88
- function listFiles(dir, base = dir) {
89
- const out = [];
90
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
91
- if (entry.name === '__pycache__') continue;
92
- const full = path.join(dir, entry.name);
93
- if (entry.isDirectory()) out.push(...listFiles(full, base));
94
- else out.push(path.relative(base, full));
95
- }
96
- return out.sort();
97
- }
98
-
99
- const digest = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
100
-
101
- function exists(p) {
102
- try {
103
- fs.lstatSync(p);
104
- return true;
105
- } catch {
106
- return false;
107
- }
108
- }
109
-
110
- function backup(dest) {
111
- // Keep the backup inside BACKUP_DIR even for folders outside ROOT (e.g. a CODEX_HOME under AppData).
112
- let rel = path.relative(ROOT, dest);
113
- if (rel.startsWith('..') || path.isAbsolute(rel)) rel = path.join('_outside', dest.replace(/^[A-Za-z]:/, (d) => d[0]));
114
- const to = path.join(BACKUP_DIR, rel);
115
- if (DRY) return console.log(` would back up ${dest} -> ${to}`);
116
- fs.mkdirSync(path.dirname(to), { recursive: true });
117
- const stat = fs.lstatSync(dest);
118
- if (stat.isSymbolicLink()) {
119
- // A link (e.g. a junction to a shared skills folder): remove only the link, never its target.
120
- fs.writeFileSync(`${to}.link.txt`, fs.readlinkSync(dest));
121
- fs.rmSync(dest, { recursive: false, force: true });
122
- } else {
123
- fs.cpSync(dest, to, { recursive: true });
124
- fs.rmSync(dest, { recursive: true, force: true });
125
- }
126
- console.log(` backed up existing copy to ${to}`);
127
- }
128
-
129
- function sameAsPackage(dir, files) {
130
- try {
131
- return fs.statSync(dir).isDirectory() &&
132
- files.every((f) => exists(path.join(dir, f)) && digest(path.join(dir, f)) === digest(path.join(SRC, f)));
133
- } catch {
134
- return false; // e.g. a dangling link
135
- }
136
- }
137
-
138
- function install() {
139
- const files = listFiles(SRC);
140
- console.log(`${DRY ? '[dry run] ' : ''}Installing ${NAME} (${SCOPE} scope, ${files.length} files)`);
141
- for (const t of TARGETS) {
142
- console.log(`- ${t.tool}: ${t.dir}`);
143
- if (exists(t.dir)) {
144
- if (sameAsPackage(t.dir, files)) {
145
- console.log(' already up to date');
146
- continue;
147
- }
148
- backup(t.dir);
149
- if (!DRY) fs.cpSync(SRC, t.dir, { recursive: true });
150
- console.log(` ${DRY ? 'would update' : 'updated'} (the previous copy is in the backup above)`);
151
- continue;
152
- }
153
- if (!DRY) fs.cpSync(SRC, t.dir, { recursive: true });
154
- console.log(` ${DRY ? 'would install' : 'installed'}`);
155
- }
156
- for (const d of legacyCodexCopies()) {
157
- if (KEEP_LEGACY) {
158
- console.log(`! old Codex copy left in place (--keep-legacy): ${d}`);
159
- continue;
160
- }
161
- console.log(`- Old Codex copy: ${d}`);
162
- backup(d);
163
- console.log(` ${DRY ? 'would remove' : 'removed'} so Codex loads only the new copy`);
164
- }
165
- warnSynced(syncedClaudeCopies());
166
- if (!DRY) console.log(`Done. Restart Claude Code / Codex, then ask an agent to "make an issue for <idea>" or run \`${NAME} verify\`.`);
167
- }
168
-
169
- function verify() {
170
- const files = listFiles(SRC);
171
- let ok = true;
172
- for (const t of TARGETS) {
173
- const missing = files.filter((f) => !exists(path.join(t.dir, f)));
174
- const changed = files.filter((f) => !missing.includes(f) && digest(path.join(t.dir, f)) !== digest(path.join(SRC, f)));
175
- const good = missing.length === 0 && changed.length === 0;
176
- ok &&= good;
177
- console.log(`${good ? 'OK ' : 'FAIL'} ${t.tool}: ${t.dir}`);
178
- for (const f of missing) console.log(` missing: ${f}`);
179
- for (const f of changed) console.log(` differs from package: ${f}`);
180
- }
181
- for (const d of legacyCodexCopies()) {
182
- ok = false;
183
- console.log(`FAIL old Codex copy still present (Codex loads it too): ${d} (run \`${NAME} install\` to remove it)`);
184
- }
185
- const synced = syncedClaudeCopies();
186
- for (const d of synced) {
187
- const current = sameAsPackage(d, listFiles(SRC));
188
- if (!current) ok = false;
189
- console.log(`${current ? 'OK ' : 'FAIL'} claude.ai account copy ${current ? 'matches the package' : 'is an older version'}: ${d}`);
190
- }
191
- if (synced.some((d) => !sameAsPackage(d, listFiles(SRC)))) warnSynced(synced);
192
- process.exit(ok ? 0 : 1);
193
- }
194
-
195
- function uninstall() {
196
- for (const t of TARGETS) {
197
- if (!exists(t.dir)) {
198
- console.log(`- ${t.tool}: not installed`);
199
- continue;
200
- }
201
- console.log(`- ${t.tool}: ${t.dir}`);
202
- backup(t.dir);
203
- }
204
- if (!KEEP_LEGACY) {
205
- for (const d of legacyCodexCopies()) {
206
- console.log(`- Old Codex copy: ${d}`);
207
- backup(d);
208
- }
209
- }
210
- warnSynced(syncedClaudeCopies());
211
- console.log(DRY ? '[dry run] nothing removed' : 'Removed. Backups are kept so nothing is lost.');
212
- }
213
-
214
- if (!fs.existsSync(path.join(SRC, 'SKILL.md'))) fail(`payload missing: ${SRC}`);
215
- const commands = { install, verify, uninstall };
216
- if (!Object.hasOwn(commands, command)) fail(`unknown command "${command}" (install | verify | uninstall)`);
217
- commands[command]();
1
+ #!/usr/bin/env node
2
+ // github-issue-builder: installs the github-issue-builder skill for Claude Code and Codex.
3
+ //
4
+ // npx github-issue-builder install [--scope user|project] [--project DIR] [--only claude|codex] [--dry-run] [--home DIR]
5
+ // [--keep-legacy] [--codex-home DIR]
6
+ // npx github-issue-builder verify [--scope user|project] [--project DIR] [--only claude|codex] [--home DIR]
7
+ // npx github-issue-builder uninstall [--scope user|project] [--project DIR] [--only claude|codex] [--dry-run] [--home DIR]
8
+ //
9
+ // Claude Code reads skills from .claude/skills and Codex from .agents/skills. The skill is identical for both,
10
+ // so the same folder is copied to each. An existing copy is moved to a timestamped backup before it is replaced.
11
+ // Old copies in <codex home>/skills are backed up and removed too (--keep-legacy skips that), and copies synced
12
+ // from a claude.ai account are reported, since only claude.ai can update them.
13
+ import fs from 'node:fs';
14
+ import os from 'node:os';
15
+ import path from 'node:path';
16
+ import crypto from 'node:crypto';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ const NAME = 'github-issue-builder';
20
+ const BUNDLE = path.dirname(fileURLToPath(import.meta.url));
21
+ const SRC = path.join(BUNDLE, 'payload', NAME);
22
+ const argv = process.argv.slice(2);
23
+ const command = argv[0] === undefined || argv[0].startsWith('--') ? 'install' : argv[0];
24
+ const hasFlag = (name) => argv.includes(name);
25
+ const optionValue = (name) => {
26
+ const i = argv.indexOf(name);
27
+ return i === -1 ? undefined : argv[i + 1];
28
+ };
29
+
30
+ const DRY = hasFlag('--dry-run');
31
+ const ONLY = optionValue('--only');
32
+ const SCOPE = optionValue('--scope') ?? 'user';
33
+ const HOME = path.resolve(optionValue('--home') ?? os.homedir());
34
+ const ROOT = SCOPE === 'project' ? path.resolve(optionValue('--project') ?? process.cwd()) : HOME;
35
+ const BACKUP_DIR = path.join(HOME, `.${NAME}-backups`, new Date().toISOString().replace(/[:.]/g, '-'));
36
+
37
+ if (!['user', 'project'].includes(SCOPE)) fail(`--scope must be user or project, got ${SCOPE}`);
38
+ if (ONLY && !['claude', 'codex'].includes(ONLY)) fail(`--only must be claude or codex, got ${ONLY}`);
39
+
40
+ const KEEP_LEGACY = hasFlag('--keep-legacy');
41
+
42
+ const TARGETS = [
43
+ { tool: 'Claude Code', key: 'claude', dir: path.join(ROOT, '.claude', 'skills', NAME) },
44
+ { tool: 'Codex', key: 'codex', dir: path.join(ROOT, '.agents', 'skills', NAME) },
45
+ ].filter((t) => !ONLY || t.key === ONLY);
46
+
47
+ // Older Codex versions (and some setups) read skills from <codex home>/skills. A copy left there makes Codex load
48
+ // the skill twice, old and new, so install and uninstall move it to the backup folder. Orca and other launchers
49
+ // point CODEX_HOME somewhere else, so that home is checked too.
50
+ function legacyCodexCopies() {
51
+ if (ONLY === 'claude') return [];
52
+ // With --home (e.g. a test sandbox) the real CODEX_HOME is ignored so nothing outside that home is touched.
53
+ const codexHome = optionValue('--codex-home') ?? (optionValue('--home') ? undefined : process.env.CODEX_HOME);
54
+ const homes = SCOPE === 'project'
55
+ ? [path.join(ROOT, '.codex')]
56
+ : [path.join(HOME, '.codex'), codexHome].filter(Boolean);
57
+ // Two homes can share one skills folder (Orca links its skills folder to ~/.codex/skills), so compare by the
58
+ // resolved folder, not the path as written.
59
+ const seen = new Set();
60
+ return homes
61
+ .map((h) => path.join(path.resolve(h), 'skills', NAME))
62
+ .filter((d) => {
63
+ const key = path.join(realpathOrNull(path.dirname(d)) ?? path.dirname(d).toLowerCase(), NAME).toLowerCase();
64
+ if (seen.has(key)) return false;
65
+ seen.add(key);
66
+ return exists(d);
67
+ });
68
+ }
69
+
70
+ // Skills added to a claude.ai account are synced into ~/.claude/skills/synced/<account>/<name>. Claude Code loads
71
+ // them next to local skills, and the sync restores anything deleted locally, so they can only be reported.
72
+ function syncedClaudeCopies() {
73
+ if (ONLY === 'codex' || SCOPE === 'project') return [];
74
+ const root = path.join(HOME, '.claude', 'skills', 'synced');
75
+ if (!exists(root)) return [];
76
+ return fs.readdirSync(root, { withFileTypes: true })
77
+ .filter((e) => e.isDirectory())
78
+ .map((e) => path.join(root, e.name, NAME))
79
+ .filter((d) => exists(path.join(d, 'SKILL.md')));
80
+ }
81
+
82
+ function warnSynced(copies) {
83
+ for (const d of copies) {
84
+ console.log(`! claude.ai account copy found: ${d}`);
85
+ console.log(' Claude Code loads it alongside this install and the claude.ai sync restores it if deleted here.');
86
+ console.log(` To finish updating, open claude.ai > Settings > Capabilities > Skills and replace or delete "${NAME}".`);
87
+ }
88
+ }
89
+
90
+ function fail(msg) {
91
+ console.error(`${NAME}: ${msg}`);
92
+ process.exit(1);
93
+ }
94
+
95
+ function listFiles(dir, base = dir) {
96
+ const out = [];
97
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
98
+ if (entry.name === '__pycache__') continue;
99
+ const full = path.join(dir, entry.name);
100
+ if (entry.isDirectory()) out.push(...listFiles(full, base));
101
+ else out.push(path.relative(base, full));
102
+ }
103
+ return out.sort();
104
+ }
105
+
106
+ const digest = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
107
+
108
+ function exists(p) {
109
+ try {
110
+ fs.lstatSync(p);
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ function backup(dest) {
118
+ // Keep the backup inside BACKUP_DIR even for folders outside ROOT (e.g. a CODEX_HOME under AppData).
119
+ let rel = path.relative(ROOT, dest);
120
+ if (rel.startsWith('..') || path.isAbsolute(rel)) rel = path.join('_outside', dest.replace(/^[A-Za-z]:/, (d) => d[0]));
121
+ const to = path.join(BACKUP_DIR, rel);
122
+ if (DRY) return console.log(` would back up ${dest} -> ${to}`);
123
+ fs.mkdirSync(path.dirname(to), { recursive: true });
124
+ const stat = fs.lstatSync(dest);
125
+ if (stat.isSymbolicLink()) {
126
+ // A link (e.g. a junction to a shared skills folder): remove only the link, never its target.
127
+ fs.writeFileSync(`${to}.link.txt`, fs.readlinkSync(dest));
128
+ fs.rmSync(dest, { recursive: false, force: true });
129
+ } else {
130
+ fs.cpSync(dest, to, { recursive: true });
131
+ fs.rmSync(dest, { recursive: true, force: true });
132
+ }
133
+ console.log(` backed up existing copy to ${to}`);
134
+ }
135
+
136
+ function sameAsPackage(dir, files) {
137
+ try {
138
+ return fs.statSync(dir).isDirectory() &&
139
+ files.every((f) => exists(path.join(dir, f)) && digest(path.join(dir, f)) === digest(path.join(SRC, f)));
140
+ } catch {
141
+ return false; // e.g. a dangling link
142
+ }
143
+ }
144
+
145
+ function realpathOrNull(p) {
146
+ try {
147
+ return fs.realpathSync(p).toLowerCase();
148
+ } catch {
149
+ return null;
150
+ }
151
+ }
152
+
153
+ // The other install target this one is a link to, if any. Every target is checked, not only the ones --only
154
+ // selected, so an --only claude install never replaces a link into the Codex folder.
155
+ function linkedTarget(t) {
156
+ if (!exists(t.dir) || !fs.lstatSync(t.dir).isSymbolicLink()) return null;
157
+ const real = realpathOrNull(t.dir);
158
+ return [
159
+ { tool: 'Claude Code', dir: path.join(ROOT, '.claude', 'skills', NAME) },
160
+ { tool: 'Codex', dir: path.join(ROOT, '.agents', 'skills', NAME) },
161
+ ].find((o) => o.dir !== t.dir && real && realpathOrNull(o.dir) === real && !fs.lstatSync(o.dir).isSymbolicLink()) ?? null;
162
+ }
163
+
164
+ function install() {
165
+ const files = listFiles(SRC);
166
+ console.log(`${DRY ? '[dry run] ' : ''}Installing ${NAME} (${SCOPE} scope, ${files.length} files)`);
167
+ for (const target of TARGETS) {
168
+ let t = target;
169
+ console.log(`- ${t.tool}: ${t.dir}`);
170
+ // A link from one target to another (e.g. ~/.claude/skills/x -> ~/.agents/skills/x, a single-source setup)
171
+ // is kept as is: updating the folder it points to updates both tools.
172
+ const other = linkedTarget(t);
173
+ if (other && TARGETS.some((o) => o.dir === other.dir)) {
174
+ console.log(` kept: it links to the ${other.tool} copy, which is updated below`);
175
+ continue;
176
+ }
177
+ if (other) {
178
+ console.log(` kept the link; updating the folder it points to: ${other.dir}`);
179
+ t = other;
180
+ }
181
+ if (exists(t.dir)) {
182
+ if (sameAsPackage(t.dir, files)) {
183
+ console.log(' already up to date');
184
+ continue;
185
+ }
186
+ backup(t.dir);
187
+ if (!DRY) fs.cpSync(SRC, t.dir, { recursive: true });
188
+ console.log(` ${DRY ? 'would update' : 'updated'} (the previous copy is in the backup above)`);
189
+ continue;
190
+ }
191
+ if (!DRY) fs.cpSync(SRC, t.dir, { recursive: true });
192
+ console.log(` ${DRY ? 'would install' : 'installed'}`);
193
+ }
194
+ for (const d of legacyCodexCopies()) {
195
+ if (!exists(d)) continue; // already removed through another path
196
+ if (KEEP_LEGACY) {
197
+ console.log(`! old Codex copy left in place (--keep-legacy): ${d}`);
198
+ continue;
199
+ }
200
+ console.log(`- Old Codex copy: ${d}`);
201
+ backup(d);
202
+ console.log(` ${DRY ? 'would remove' : 'removed'} so Codex loads only the new copy`);
203
+ }
204
+ warnSynced(syncedClaudeCopies());
205
+ if (!DRY) console.log(`Done. Restart Claude Code / Codex, then ask an agent to "make an issue for <idea>" or run \`${NAME} verify\`.`);
206
+ }
207
+
208
+ function verify() {
209
+ const files = listFiles(SRC);
210
+ let ok = true;
211
+ for (const t of TARGETS) {
212
+ const missing = files.filter((f) => !exists(path.join(t.dir, f)));
213
+ const changed = files.filter((f) => !missing.includes(f) && digest(path.join(t.dir, f)) !== digest(path.join(SRC, f)));
214
+ const good = missing.length === 0 && changed.length === 0;
215
+ ok &&= good;
216
+ console.log(`${good ? 'OK ' : 'FAIL'} ${t.tool}: ${t.dir}`);
217
+ for (const f of missing) console.log(` missing: ${f}`);
218
+ for (const f of changed) console.log(` differs from package: ${f}`);
219
+ }
220
+ for (const d of legacyCodexCopies()) {
221
+ ok = false;
222
+ console.log(`FAIL old Codex copy still present (Codex loads it too): ${d} (run \`${NAME} install\` to remove it)`);
223
+ }
224
+ const synced = syncedClaudeCopies();
225
+ for (const d of synced) {
226
+ const current = sameAsPackage(d, listFiles(SRC));
227
+ if (!current) ok = false;
228
+ console.log(`${current ? 'OK ' : 'FAIL'} claude.ai account copy ${current ? 'matches the package' : 'is an older version'}: ${d}`);
229
+ }
230
+ if (synced.some((d) => !sameAsPackage(d, listFiles(SRC)))) warnSynced(synced);
231
+ process.exit(ok ? 0 : 1);
232
+ }
233
+
234
+ function uninstall() {
235
+ for (const t of TARGETS) {
236
+ if (!exists(t.dir)) {
237
+ console.log(`- ${t.tool}: not installed`);
238
+ continue;
239
+ }
240
+ console.log(`- ${t.tool}: ${t.dir}`);
241
+ backup(t.dir);
242
+ }
243
+ if (!KEEP_LEGACY) {
244
+ for (const d of legacyCodexCopies()) {
245
+ if (!exists(d)) continue;
246
+ console.log(`- Old Codex copy: ${d}`);
247
+ backup(d);
248
+ }
249
+ }
250
+ warnSynced(syncedClaudeCopies());
251
+ console.log(DRY ? '[dry run] nothing removed' : 'Removed. Backups are kept so nothing is lost.');
252
+ }
253
+
254
+ if (!fs.existsSync(path.join(SRC, 'SKILL.md'))) fail(`payload missing: ${SRC}`);
255
+ const commands = { install, verify, uninstall };
256
+ if (!Object.hasOwn(commands, command)) fail(`unknown command "${command}" (install | verify | uninstall)`);
257
+ commands[command]();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-builder",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Agent skill for Claude Code and Codex: turn a rough feature or bug idea into an agent-ready GitHub issue (or epic + child issues) with spec, acceptance criteria, plan, test plan and branch/worktree setup",
5
5
  "type": "module",
6
6
  "bin": {