@fermindi/pwn-cli 0.1.1 → 0.3.0

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 (48) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +265 -251
  3. package/cli/batch.js +333 -333
  4. package/cli/codespaces.js +303 -303
  5. package/cli/index.js +112 -91
  6. package/cli/inject.js +90 -67
  7. package/cli/knowledge.js +531 -531
  8. package/cli/migrate.js +466 -0
  9. package/cli/notify.js +135 -135
  10. package/cli/patterns.js +665 -665
  11. package/cli/save.js +206 -0
  12. package/cli/status.js +91 -91
  13. package/cli/update.js +189 -0
  14. package/cli/validate.js +61 -61
  15. package/package.json +70 -70
  16. package/src/core/inject.js +300 -204
  17. package/src/core/state.js +91 -91
  18. package/src/core/validate.js +202 -202
  19. package/src/core/workspace.js +176 -176
  20. package/src/index.js +20 -20
  21. package/src/knowledge/gc.js +308 -308
  22. package/src/knowledge/lifecycle.js +401 -401
  23. package/src/knowledge/promote.js +364 -364
  24. package/src/knowledge/references.js +342 -342
  25. package/src/patterns/matcher.js +218 -218
  26. package/src/patterns/registry.js +375 -375
  27. package/src/patterns/triggers.js +423 -423
  28. package/src/services/batch-service.js +849 -849
  29. package/src/services/notification-service.js +342 -342
  30. package/templates/codespaces/devcontainer.json +52 -52
  31. package/templates/codespaces/setup.sh +70 -70
  32. package/templates/workspace/.ai/README.md +164 -164
  33. package/templates/workspace/.ai/agents/README.md +204 -204
  34. package/templates/workspace/.ai/agents/claude.md +625 -625
  35. package/templates/workspace/.ai/config/README.md +79 -79
  36. package/templates/workspace/.ai/config/notifications.template.json +20 -20
  37. package/templates/workspace/.ai/memory/deadends.md +79 -79
  38. package/templates/workspace/.ai/memory/decisions.md +58 -58
  39. package/templates/workspace/.ai/memory/patterns.md +65 -65
  40. package/templates/workspace/.ai/patterns/backend/README.md +126 -126
  41. package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
  42. package/templates/workspace/.ai/patterns/index.md +256 -256
  43. package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
  44. package/templates/workspace/.ai/patterns/universal/README.md +141 -141
  45. package/templates/workspace/.ai/state.template.json +8 -8
  46. package/templates/workspace/.ai/tasks/active.md +77 -77
  47. package/templates/workspace/.ai/tasks/backlog.md +95 -95
  48. package/templates/workspace/.ai/workflows/batch-task.md +356 -356
package/cli/codespaces.js CHANGED
@@ -1,303 +1,303 @@
1
- import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, chmodSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { execSync } from 'child_process';
5
-
6
- const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const cwd = process.cwd();
8
-
9
- export default async function codespaces(args) {
10
- const subcommand = args[0];
11
-
12
- switch (subcommand) {
13
- case 'init':
14
- await initCodespaces(args.slice(1));
15
- break;
16
-
17
- case 'status':
18
- await showStatus();
19
- break;
20
-
21
- case 'open':
22
- await openCodespace(args.slice(1));
23
- break;
24
-
25
- case 'list':
26
- await listCodespaces();
27
- break;
28
-
29
- default:
30
- showHelp();
31
- }
32
- }
33
-
34
- function showHelp() {
35
- console.log(`
36
- PWN Codespaces Integration
37
-
38
- Usage: pwn codespaces <command> [options]
39
-
40
- Commands:
41
- init Add devcontainer config to current project
42
- init --minimal Add minimal devcontainer (no extras)
43
- init --docker Use custom Dockerfile
44
- status Check codespaces configuration
45
- open Open project in GitHub Codespace
46
- list List active codespaces (requires gh CLI)
47
-
48
- Examples:
49
- pwn codespaces init
50
- pwn codespaces init --minimal
51
- pwn codespaces open
52
- pwn codespaces status
53
-
54
- Note: Requires GitHub CLI (gh) for some features.
55
- Install: https://cli.github.com/
56
- `);
57
- }
58
-
59
- async function initCodespaces(args) {
60
- const minimal = args.includes('--minimal');
61
- const useDockerfile = args.includes('--docker');
62
-
63
- console.log('Setting up Codespaces configuration...\n');
64
-
65
- // Check if .devcontainer already exists
66
- const devcontainerDir = join(cwd, '.devcontainer');
67
-
68
- if (existsSync(devcontainerDir)) {
69
- if (!args.includes('--force')) {
70
- console.log('.devcontainer/ already exists.');
71
- console.log('Use --force to overwrite.');
72
- return;
73
- }
74
- console.log('Overwriting existing .devcontainer/...');
75
- }
76
-
77
- // Create directory
78
- mkdirSync(devcontainerDir, { recursive: true });
79
-
80
- // Copy template files
81
- const templateDir = join(__dirname, '..', 'templates', 'codespaces');
82
-
83
- if (minimal) {
84
- // Minimal config - just devcontainer.json
85
- const minimalConfig = {
86
- name: getProjectName(),
87
- image: 'mcr.microsoft.com/devcontainers/javascript-node:22',
88
- postCreateCommand: 'npm install',
89
- forwardPorts: [3000, 5173],
90
- remoteUser: 'node'
91
- };
92
-
93
- writeFileSync(
94
- join(devcontainerDir, 'devcontainer.json'),
95
- JSON.stringify(minimalConfig, null, 2)
96
- );
97
- console.log('Created minimal devcontainer.json');
98
-
99
- } else if (useDockerfile) {
100
- // Copy full template with Dockerfile reference
101
- const pwmDevcontainerDir = join(__dirname, '..', '.devcontainer');
102
-
103
- cpSync(join(pwmDevcontainerDir, 'Dockerfile'), join(devcontainerDir, 'Dockerfile'));
104
- cpSync(join(pwmDevcontainerDir, 'devcontainer.dockerfile.json'), join(devcontainerDir, 'devcontainer.json'));
105
- cpSync(join(pwmDevcontainerDir, 'setup-codespace.sh'), join(devcontainerDir, 'setup.sh'));
106
-
107
- console.log('Created devcontainer.json (with Dockerfile)');
108
- console.log('Created Dockerfile');
109
- console.log('Created setup.sh');
110
-
111
- } else {
112
- // Standard setup from templates
113
- if (existsSync(templateDir)) {
114
- cpSync(join(templateDir, 'devcontainer.json'), join(devcontainerDir, 'devcontainer.json'));
115
- cpSync(join(templateDir, 'setup.sh'), join(devcontainerDir, 'setup.sh'));
116
- } else {
117
- // Fallback: copy from PWN's own devcontainer
118
- const pwmDevcontainerDir = join(__dirname, '..', '.devcontainer');
119
- cpSync(join(pwmDevcontainerDir, 'devcontainer.json'), join(devcontainerDir, 'devcontainer.json'));
120
- cpSync(join(pwmDevcontainerDir, 'setup-codespace.sh'), join(devcontainerDir, 'setup.sh'));
121
- }
122
-
123
- // Update project name in devcontainer.json
124
- const configPath = join(devcontainerDir, 'devcontainer.json');
125
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
126
- config.name = getProjectName();
127
- writeFileSync(configPath, JSON.stringify(config, null, 2));
128
-
129
- console.log('Created devcontainer.json');
130
- console.log('Created setup.sh');
131
- }
132
-
133
- // Make setup.sh executable (on Unix systems)
134
- try {
135
- const setupPath = join(devcontainerDir, 'setup.sh');
136
- if (existsSync(setupPath)) {
137
- chmodSync(setupPath, '755');
138
- }
139
- } catch {
140
- // Windows doesn't support chmod
141
- }
142
-
143
- console.log('\nCodespaces configuration ready!');
144
- console.log('\nNext steps:');
145
- console.log(' 1. Commit the .devcontainer/ folder');
146
- console.log(' 2. Push to GitHub');
147
- console.log(' 3. Open in Codespace: gh codespace create');
148
- console.log('');
149
- }
150
-
151
- async function showStatus() {
152
- console.log('Codespaces Configuration Status\n');
153
-
154
- const devcontainerDir = join(cwd, '.devcontainer');
155
- const configPath = join(devcontainerDir, 'devcontainer.json');
156
-
157
- // Check devcontainer
158
- if (!existsSync(devcontainerDir)) {
159
- console.log('Status: Not configured');
160
- console.log('\nRun: pwn codespaces init');
161
- return;
162
- }
163
-
164
- console.log('Status: Configured');
165
- console.log(`Path: ${devcontainerDir}`);
166
-
167
- // Parse config
168
- if (existsSync(configPath)) {
169
- try {
170
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
171
- console.log(`\nContainer: ${config.name || 'Unnamed'}`);
172
- console.log(`Image: ${config.image || 'Custom Dockerfile'}`);
173
-
174
- if (config.forwardPorts?.length) {
175
- console.log(`Ports: ${config.forwardPorts.join(', ')}`);
176
- }
177
-
178
- if (config.customizations?.vscode?.extensions?.length) {
179
- console.log(`Extensions: ${config.customizations.vscode.extensions.length} configured`);
180
- }
181
- } catch {
182
- console.log('Warning: Could not parse devcontainer.json');
183
- }
184
- }
185
-
186
- // Check for Dockerfile
187
- if (existsSync(join(devcontainerDir, 'Dockerfile'))) {
188
- console.log('Dockerfile: Yes (custom build)');
189
- }
190
-
191
- // Check for setup script
192
- if (existsSync(join(devcontainerDir, 'setup.sh'))) {
193
- console.log('Setup script: Yes');
194
- }
195
-
196
- // Check if in a codespace
197
- if (process.env.PWN_CODESPACE === 'true' || process.env.CODESPACES === 'true') {
198
- console.log('\nCurrently running in a Codespace!');
199
- }
200
-
201
- // Check GitHub remote
202
- try {
203
- const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
204
- if (remote.includes('github.com')) {
205
- const match = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
206
- if (match) {
207
- console.log(`\nGitHub: ${match[1]}/${match[2]}`);
208
- console.log(`Open: https://github.com/codespaces/new?repo=${match[1]}/${match[2]}`);
209
- }
210
- }
211
- } catch {
212
- // Not a git repo or no remote
213
- }
214
-
215
- console.log('');
216
- }
217
-
218
- async function openCodespace(args) {
219
- // Check for gh CLI
220
- try {
221
- execSync('gh --version', { stdio: 'ignore' });
222
- } catch {
223
- console.log('GitHub CLI (gh) not found.');
224
- console.log('Install from: https://cli.github.com/');
225
- return;
226
- }
227
-
228
- // Check auth
229
- try {
230
- execSync('gh auth status', { stdio: 'ignore' });
231
- } catch {
232
- console.log('Not authenticated with GitHub CLI.');
233
- console.log('Run: gh auth login');
234
- return;
235
- }
236
-
237
- console.log('Opening project in Codespace...\n');
238
-
239
- try {
240
- // Check if there's an existing codespace
241
- const existing = execSync('gh codespace list --json name,repository -q ".[] | select(.repository | contains(\\"' + getRepoName() + '\\"))"', {
242
- encoding: 'utf8'
243
- }).trim();
244
-
245
- if (existing) {
246
- console.log('Found existing codespace. Opening...');
247
- execSync('gh codespace code', { stdio: 'inherit' });
248
- } else {
249
- console.log('Creating new codespace...');
250
- execSync('gh codespace create --repo ' + getRepoName(), { stdio: 'inherit' });
251
- }
252
- } catch (error) {
253
- console.log('Error:', error.message);
254
- console.log('\nTry manually: gh codespace create');
255
- }
256
- }
257
-
258
- async function listCodespaces() {
259
- try {
260
- execSync('gh --version', { stdio: 'ignore' });
261
- } catch {
262
- console.log('GitHub CLI (gh) not found.');
263
- console.log('Install from: https://cli.github.com/');
264
- return;
265
- }
266
-
267
- console.log('Active Codespaces:\n');
268
-
269
- try {
270
- execSync('gh codespace list', { stdio: 'inherit' });
271
- } catch {
272
- console.log('Could not list codespaces. Make sure you are authenticated.');
273
- console.log('Run: gh auth login');
274
- }
275
- }
276
-
277
- function getProjectName() {
278
- try {
279
- const pkgPath = join(cwd, 'package.json');
280
- if (existsSync(pkgPath)) {
281
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
282
- return pkg.name || 'Development';
283
- }
284
- } catch {
285
- // ignore
286
- }
287
-
288
- // Use directory name
289
- return cwd.split(/[/\\]/).pop() || 'Development';
290
- }
291
-
292
- function getRepoName() {
293
- try {
294
- const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
295
- const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
296
- if (match) {
297
- return match[1].replace('.git', '');
298
- }
299
- } catch {
300
- // ignore
301
- }
302
- return '';
303
- }
1
+ import { existsSync, mkdirSync, cpSync, readFileSync, writeFileSync, chmodSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync } from 'child_process';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const cwd = process.cwd();
8
+
9
+ export default async function codespaces(args) {
10
+ const subcommand = args[0];
11
+
12
+ switch (subcommand) {
13
+ case 'init':
14
+ await initCodespaces(args.slice(1));
15
+ break;
16
+
17
+ case 'status':
18
+ await showStatus();
19
+ break;
20
+
21
+ case 'open':
22
+ await openCodespace(args.slice(1));
23
+ break;
24
+
25
+ case 'list':
26
+ await listCodespaces();
27
+ break;
28
+
29
+ default:
30
+ showHelp();
31
+ }
32
+ }
33
+
34
+ function showHelp() {
35
+ console.log(`
36
+ PWN Codespaces Integration
37
+
38
+ Usage: pwn codespaces <command> [options]
39
+
40
+ Commands:
41
+ init Add devcontainer config to current project
42
+ init --minimal Add minimal devcontainer (no extras)
43
+ init --docker Use custom Dockerfile
44
+ status Check codespaces configuration
45
+ open Open project in GitHub Codespace
46
+ list List active codespaces (requires gh CLI)
47
+
48
+ Examples:
49
+ pwn codespaces init
50
+ pwn codespaces init --minimal
51
+ pwn codespaces open
52
+ pwn codespaces status
53
+
54
+ Note: Requires GitHub CLI (gh) for some features.
55
+ Install: https://cli.github.com/
56
+ `);
57
+ }
58
+
59
+ async function initCodespaces(args) {
60
+ const minimal = args.includes('--minimal');
61
+ const useDockerfile = args.includes('--docker');
62
+
63
+ console.log('Setting up Codespaces configuration...\n');
64
+
65
+ // Check if .devcontainer already exists
66
+ const devcontainerDir = join(cwd, '.devcontainer');
67
+
68
+ if (existsSync(devcontainerDir)) {
69
+ if (!args.includes('--force')) {
70
+ console.log('.devcontainer/ already exists.');
71
+ console.log('Use --force to overwrite.');
72
+ return;
73
+ }
74
+ console.log('Overwriting existing .devcontainer/...');
75
+ }
76
+
77
+ // Create directory
78
+ mkdirSync(devcontainerDir, { recursive: true });
79
+
80
+ // Copy template files
81
+ const templateDir = join(__dirname, '..', 'templates', 'codespaces');
82
+
83
+ if (minimal) {
84
+ // Minimal config - just devcontainer.json
85
+ const minimalConfig = {
86
+ name: getProjectName(),
87
+ image: 'mcr.microsoft.com/devcontainers/javascript-node:22',
88
+ postCreateCommand: 'npm install',
89
+ forwardPorts: [3000, 5173],
90
+ remoteUser: 'node'
91
+ };
92
+
93
+ writeFileSync(
94
+ join(devcontainerDir, 'devcontainer.json'),
95
+ JSON.stringify(minimalConfig, null, 2)
96
+ );
97
+ console.log('Created minimal devcontainer.json');
98
+
99
+ } else if (useDockerfile) {
100
+ // Copy full template with Dockerfile reference
101
+ const pwmDevcontainerDir = join(__dirname, '..', '.devcontainer');
102
+
103
+ cpSync(join(pwmDevcontainerDir, 'Dockerfile'), join(devcontainerDir, 'Dockerfile'));
104
+ cpSync(join(pwmDevcontainerDir, 'devcontainer.dockerfile.json'), join(devcontainerDir, 'devcontainer.json'));
105
+ cpSync(join(pwmDevcontainerDir, 'setup-codespace.sh'), join(devcontainerDir, 'setup.sh'));
106
+
107
+ console.log('Created devcontainer.json (with Dockerfile)');
108
+ console.log('Created Dockerfile');
109
+ console.log('Created setup.sh');
110
+
111
+ } else {
112
+ // Standard setup from templates
113
+ if (existsSync(templateDir)) {
114
+ cpSync(join(templateDir, 'devcontainer.json'), join(devcontainerDir, 'devcontainer.json'));
115
+ cpSync(join(templateDir, 'setup.sh'), join(devcontainerDir, 'setup.sh'));
116
+ } else {
117
+ // Fallback: copy from PWN's own devcontainer
118
+ const pwmDevcontainerDir = join(__dirname, '..', '.devcontainer');
119
+ cpSync(join(pwmDevcontainerDir, 'devcontainer.json'), join(devcontainerDir, 'devcontainer.json'));
120
+ cpSync(join(pwmDevcontainerDir, 'setup-codespace.sh'), join(devcontainerDir, 'setup.sh'));
121
+ }
122
+
123
+ // Update project name in devcontainer.json
124
+ const configPath = join(devcontainerDir, 'devcontainer.json');
125
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
126
+ config.name = getProjectName();
127
+ writeFileSync(configPath, JSON.stringify(config, null, 2));
128
+
129
+ console.log('Created devcontainer.json');
130
+ console.log('Created setup.sh');
131
+ }
132
+
133
+ // Make setup.sh executable (on Unix systems)
134
+ try {
135
+ const setupPath = join(devcontainerDir, 'setup.sh');
136
+ if (existsSync(setupPath)) {
137
+ chmodSync(setupPath, '755');
138
+ }
139
+ } catch {
140
+ // Windows doesn't support chmod
141
+ }
142
+
143
+ console.log('\nCodespaces configuration ready!');
144
+ console.log('\nNext steps:');
145
+ console.log(' 1. Commit the .devcontainer/ folder');
146
+ console.log(' 2. Push to GitHub');
147
+ console.log(' 3. Open in Codespace: gh codespace create');
148
+ console.log('');
149
+ }
150
+
151
+ async function showStatus() {
152
+ console.log('Codespaces Configuration Status\n');
153
+
154
+ const devcontainerDir = join(cwd, '.devcontainer');
155
+ const configPath = join(devcontainerDir, 'devcontainer.json');
156
+
157
+ // Check devcontainer
158
+ if (!existsSync(devcontainerDir)) {
159
+ console.log('Status: Not configured');
160
+ console.log('\nRun: pwn codespaces init');
161
+ return;
162
+ }
163
+
164
+ console.log('Status: Configured');
165
+ console.log(`Path: ${devcontainerDir}`);
166
+
167
+ // Parse config
168
+ if (existsSync(configPath)) {
169
+ try {
170
+ const config = JSON.parse(readFileSync(configPath, 'utf8'));
171
+ console.log(`\nContainer: ${config.name || 'Unnamed'}`);
172
+ console.log(`Image: ${config.image || 'Custom Dockerfile'}`);
173
+
174
+ if (config.forwardPorts?.length) {
175
+ console.log(`Ports: ${config.forwardPorts.join(', ')}`);
176
+ }
177
+
178
+ if (config.customizations?.vscode?.extensions?.length) {
179
+ console.log(`Extensions: ${config.customizations.vscode.extensions.length} configured`);
180
+ }
181
+ } catch {
182
+ console.log('Warning: Could not parse devcontainer.json');
183
+ }
184
+ }
185
+
186
+ // Check for Dockerfile
187
+ if (existsSync(join(devcontainerDir, 'Dockerfile'))) {
188
+ console.log('Dockerfile: Yes (custom build)');
189
+ }
190
+
191
+ // Check for setup script
192
+ if (existsSync(join(devcontainerDir, 'setup.sh'))) {
193
+ console.log('Setup script: Yes');
194
+ }
195
+
196
+ // Check if in a codespace
197
+ if (process.env.PWN_CODESPACE === 'true' || process.env.CODESPACES === 'true') {
198
+ console.log('\nCurrently running in a Codespace!');
199
+ }
200
+
201
+ // Check GitHub remote
202
+ try {
203
+ const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
204
+ if (remote.includes('github.com')) {
205
+ const match = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/);
206
+ if (match) {
207
+ console.log(`\nGitHub: ${match[1]}/${match[2]}`);
208
+ console.log(`Open: https://github.com/codespaces/new?repo=${match[1]}/${match[2]}`);
209
+ }
210
+ }
211
+ } catch {
212
+ // Not a git repo or no remote
213
+ }
214
+
215
+ console.log('');
216
+ }
217
+
218
+ async function openCodespace(args) {
219
+ // Check for gh CLI
220
+ try {
221
+ execSync('gh --version', { stdio: 'ignore' });
222
+ } catch {
223
+ console.log('GitHub CLI (gh) not found.');
224
+ console.log('Install from: https://cli.github.com/');
225
+ return;
226
+ }
227
+
228
+ // Check auth
229
+ try {
230
+ execSync('gh auth status', { stdio: 'ignore' });
231
+ } catch {
232
+ console.log('Not authenticated with GitHub CLI.');
233
+ console.log('Run: gh auth login');
234
+ return;
235
+ }
236
+
237
+ console.log('Opening project in Codespace...\n');
238
+
239
+ try {
240
+ // Check if there's an existing codespace
241
+ const existing = execSync('gh codespace list --json name,repository -q ".[] | select(.repository | contains(\\"' + getRepoName() + '\\"))"', {
242
+ encoding: 'utf8'
243
+ }).trim();
244
+
245
+ if (existing) {
246
+ console.log('Found existing codespace. Opening...');
247
+ execSync('gh codespace code', { stdio: 'inherit' });
248
+ } else {
249
+ console.log('Creating new codespace...');
250
+ execSync('gh codespace create --repo ' + getRepoName(), { stdio: 'inherit' });
251
+ }
252
+ } catch (error) {
253
+ console.log('Error:', error.message);
254
+ console.log('\nTry manually: gh codespace create');
255
+ }
256
+ }
257
+
258
+ async function listCodespaces() {
259
+ try {
260
+ execSync('gh --version', { stdio: 'ignore' });
261
+ } catch {
262
+ console.log('GitHub CLI (gh) not found.');
263
+ console.log('Install from: https://cli.github.com/');
264
+ return;
265
+ }
266
+
267
+ console.log('Active Codespaces:\n');
268
+
269
+ try {
270
+ execSync('gh codespace list', { stdio: 'inherit' });
271
+ } catch {
272
+ console.log('Could not list codespaces. Make sure you are authenticated.');
273
+ console.log('Run: gh auth login');
274
+ }
275
+ }
276
+
277
+ function getProjectName() {
278
+ try {
279
+ const pkgPath = join(cwd, 'package.json');
280
+ if (existsSync(pkgPath)) {
281
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
282
+ return pkg.name || 'Development';
283
+ }
284
+ } catch {
285
+ // ignore
286
+ }
287
+
288
+ // Use directory name
289
+ return cwd.split(/[/\\]/).pop() || 'Development';
290
+ }
291
+
292
+ function getRepoName() {
293
+ try {
294
+ const remote = execSync('git remote get-url origin', { encoding: 'utf8' }).trim();
295
+ const match = remote.match(/github\.com[:/]([^/]+\/[^/.]+)/);
296
+ if (match) {
297
+ return match[1].replace('.git', '');
298
+ }
299
+ } catch {
300
+ // ignore
301
+ }
302
+ return '';
303
+ }