@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.
- package/LICENSE +21 -21
- package/README.md +265 -251
- package/cli/batch.js +333 -333
- package/cli/codespaces.js +303 -303
- package/cli/index.js +112 -91
- package/cli/inject.js +90 -67
- package/cli/knowledge.js +531 -531
- package/cli/migrate.js +466 -0
- package/cli/notify.js +135 -135
- package/cli/patterns.js +665 -665
- package/cli/save.js +206 -0
- package/cli/status.js +91 -91
- package/cli/update.js +189 -0
- package/cli/validate.js +61 -61
- package/package.json +70 -70
- package/src/core/inject.js +300 -204
- package/src/core/state.js +91 -91
- package/src/core/validate.js +202 -202
- package/src/core/workspace.js +176 -176
- package/src/index.js +20 -20
- package/src/knowledge/gc.js +308 -308
- package/src/knowledge/lifecycle.js +401 -401
- package/src/knowledge/promote.js +364 -364
- package/src/knowledge/references.js +342 -342
- package/src/patterns/matcher.js +218 -218
- package/src/patterns/registry.js +375 -375
- package/src/patterns/triggers.js +423 -423
- package/src/services/batch-service.js +849 -849
- package/src/services/notification-service.js +342 -342
- package/templates/codespaces/devcontainer.json +52 -52
- package/templates/codespaces/setup.sh +70 -70
- package/templates/workspace/.ai/README.md +164 -164
- package/templates/workspace/.ai/agents/README.md +204 -204
- package/templates/workspace/.ai/agents/claude.md +625 -625
- package/templates/workspace/.ai/config/README.md +79 -79
- package/templates/workspace/.ai/config/notifications.template.json +20 -20
- package/templates/workspace/.ai/memory/deadends.md +79 -79
- package/templates/workspace/.ai/memory/decisions.md +58 -58
- package/templates/workspace/.ai/memory/patterns.md +65 -65
- package/templates/workspace/.ai/patterns/backend/README.md +126 -126
- package/templates/workspace/.ai/patterns/frontend/README.md +103 -103
- package/templates/workspace/.ai/patterns/index.md +256 -256
- package/templates/workspace/.ai/patterns/triggers.json +1087 -1087
- package/templates/workspace/.ai/patterns/universal/README.md +141 -141
- package/templates/workspace/.ai/state.template.json +8 -8
- package/templates/workspace/.ai/tasks/active.md +77 -77
- package/templates/workspace/.ai/tasks/backlog.md +95 -95
- 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
|
+
}
|