@fermindi/pwn-cli 0.5.0 → 0.7.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/README.md +14 -10
- package/cli/backlog.js +60 -0
- package/cli/batch.js +112 -12
- package/cli/index.js +9 -31
- package/cli/inject.js +8 -32
- package/cli/status.js +1 -1
- package/cli/update.js +78 -27
- package/package.json +6 -3
- package/src/core/inject.js +41 -45
- package/src/core/state.js +0 -1
- package/src/core/validate.js +14 -1
- package/src/core/workspace.js +13 -11
- package/src/index.js +0 -1
- package/src/services/batch-runner.js +769 -0
- package/src/services/batch-service.js +185 -74
- package/src/ui/backlog-viewer.js +394 -0
- package/templates/workspace/.ai/agents/claude.md +47 -146
- package/templates/workspace/.ai/batch/tasks/.gitkeep +0 -0
- package/templates/workspace/.ai/memory/patterns.md +57 -11
- package/templates/workspace/.ai/tasks/active.md +1 -1
- package/templates/workspace/.ai/workflows/batch-task.md +43 -67
- package/templates/workspace/.claude/commands/save.md +0 -42
- package/cli/codespaces.js +0 -303
- package/cli/migrate.js +0 -466
- package/cli/mode.js +0 -206
- package/cli/notify.js +0 -135
- package/src/services/notification-service.js +0 -342
- package/templates/codespaces/devcontainer.json +0 -52
- package/templates/codespaces/setup.sh +0 -70
- package/templates/workspace/.ai/config/notifications.template.json +0 -20
- package/templates/workspace/.ai/tasks/backlog.md +0 -95
- package/templates/workspace/.claude/commands/mode.md +0 -103
- package/templates/workspace/.claude/settings.json +0 -15
|
@@ -1,19 +1,65 @@
|
|
|
1
|
-
# Codebase Patterns
|
|
1
|
+
# Codebase Patterns
|
|
2
2
|
|
|
3
|
-
This file
|
|
4
|
-
Each entry links to the full pattern file.
|
|
3
|
+
This file documents recurring patterns discovered during development.
|
|
5
4
|
|
|
6
|
-
##
|
|
5
|
+
## Format
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
<!-- Format: - [{category}/{name}](.ai/patterns/{category}/{name}.md) - Brief description -->
|
|
7
|
+
Each pattern follows this structure:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
```markdown
|
|
10
|
+
## Pattern Category
|
|
11
|
+
|
|
12
|
+
### Pattern Name
|
|
13
|
+
**Context:** When this pattern applies
|
|
14
|
+
**Pattern:** Code example or description
|
|
15
|
+
**Rationale:** Why this is the better approach
|
|
16
|
+
**Example Location:** Where to find real examples
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Add New Patterns
|
|
22
|
+
|
|
23
|
+
When you discover a useful pattern:
|
|
24
|
+
|
|
25
|
+
1. Identify the category (Frontend, Backend, Testing, etc.)
|
|
26
|
+
2. Fill in all sections
|
|
27
|
+
3. Add code example if possible
|
|
28
|
+
4. Commit with message: `docs: add pattern - [name]`
|
|
29
|
+
5. Update `patterns/index.md` if this should be auto-applied
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Pattern Categories
|
|
34
|
+
|
|
35
|
+
- **Frontend:** React, TypeScript, styling, component patterns
|
|
36
|
+
- **Backend:** API design, database queries, middleware
|
|
37
|
+
- **Testing:** Unit tests, integration tests, fixtures
|
|
38
|
+
- **Performance:** Caching, memoization, optimization
|
|
39
|
+
- **Security:** Input validation, authentication, authorization
|
|
40
|
+
- **Error Handling:** Exception patterns, recovery strategies
|
|
41
|
+
- **Data Structures:** Modeling domain objects effectively
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Template for New Pattern
|
|
46
|
+
|
|
47
|
+
```markdown
|
|
48
|
+
## [Category]
|
|
49
|
+
|
|
50
|
+
### [Pattern Name]
|
|
51
|
+
**Context:** (When to use this pattern)
|
|
52
|
+
**Pattern:** (Description or code example)
|
|
53
|
+
**Rationale:** (Why this is better)
|
|
54
|
+
**Example Location:** (Where in codebase)
|
|
55
|
+
```
|
|
12
56
|
|
|
13
57
|
---
|
|
14
58
|
|
|
15
|
-
##
|
|
59
|
+
## Notes
|
|
16
60
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
61
|
+
- Patterns should be language-agnostic when possible
|
|
62
|
+
- Prioritize patterns that prevent common bugs
|
|
63
|
+
- Link related patterns together
|
|
64
|
+
- Update this file as team learns new approaches
|
|
65
|
+
- Archive rarely-used patterns after 3 months of no references
|
|
@@ -70,7 +70,7 @@ Highlight 1-3 most important tasks for today.
|
|
|
70
70
|
|
|
71
71
|
## Notes
|
|
72
72
|
|
|
73
|
-
- Check
|
|
73
|
+
- Check prd.json for upcoming stories
|
|
74
74
|
- Move completed tasks to archive or backlog with completion date
|
|
75
75
|
- When stuck, create SPIKE task to investigate
|
|
76
76
|
- Reference decisions from `memory/decisions.md`
|
|
@@ -4,103 +4,79 @@ This file defines the template for autonomous batch task execution via the `pwn
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Batch task execution
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
Batch task execution uses `.ai/batch/batch_runner.sh` to autonomously:
|
|
8
|
+
- Read stories from `.ai/tasks/prd.json`
|
|
9
|
+
- Spawn fresh Claude sessions per task
|
|
10
10
|
- Run quality gates (tests, linting, type checking)
|
|
11
|
-
-
|
|
12
|
-
-
|
|
11
|
+
- Track progress and learnings between iterations
|
|
12
|
+
- Handle retries, rate limits, and circuit breaking
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
#
|
|
18
|
-
|
|
17
|
+
# Preview next task
|
|
18
|
+
./.ai/batch/batch_runner.sh --dry-run
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
|
|
20
|
+
# Run batch (default 20 iterations)
|
|
21
|
+
./.ai/batch/batch_runner.sh
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
|
|
23
|
+
# Custom iteration limit
|
|
24
|
+
./.ai/batch/batch_runner.sh 50
|
|
25
25
|
|
|
26
|
-
#
|
|
27
|
-
|
|
26
|
+
# Specific phase only
|
|
27
|
+
./.ai/batch/batch_runner.sh --phase 3
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
pwn batch
|
|
29
|
+
# Via pwn CLI
|
|
30
|
+
pwn batch run
|
|
31
|
+
pwn batch run --dry-run
|
|
32
|
+
pwn batch status
|
|
31
33
|
```
|
|
32
34
|
|
|
33
35
|
## Batch Execution Protocol
|
|
34
36
|
|
|
35
37
|
### Phase 1: Task Selection
|
|
36
38
|
|
|
37
|
-
1. Read `tasks/
|
|
38
|
-
2.
|
|
39
|
-
3.
|
|
40
|
-
4.
|
|
41
|
-
5. Verify dependencies are met
|
|
42
|
-
6. Move to `active.md`
|
|
39
|
+
1. Read `tasks/prd.json` for stories
|
|
40
|
+
2. Find next story where `passes == false`
|
|
41
|
+
3. Verify all dependencies have `passes == true`
|
|
42
|
+
4. Apply phase filter if specified
|
|
43
43
|
|
|
44
44
|
### Phase 2: Execution
|
|
45
45
|
|
|
46
|
-
1.
|
|
47
|
-
2.
|
|
48
|
-
3.
|
|
49
|
-
4.
|
|
50
|
-
5. Keep commits atomic and descriptive
|
|
46
|
+
1. Build prompt from `.ai/batch/prompt.md` template
|
|
47
|
+
2. Substitute story placeholders ({STORY_ID}, {STORY_TITLE}, etc.)
|
|
48
|
+
3. Spawn: `claude --print --dangerously-skip-permissions -p "<prompt>"`
|
|
49
|
+
4. Capture output to `logs/` for debugging
|
|
51
50
|
|
|
52
51
|
### Phase 3: Quality Gates
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
Customize `run_quality_gates()` in `batch_runner.sh` for your stack:
|
|
55
54
|
|
|
55
|
+
**Node.js:**
|
|
56
56
|
```bash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
[ ] npm run lint || eslint .
|
|
62
|
-
|
|
63
|
-
# Unit tests
|
|
64
|
-
[ ] npm run test || jest
|
|
65
|
-
|
|
66
|
-
# Integration tests (if applicable)
|
|
67
|
-
[ ] npm run test:integration
|
|
68
|
-
|
|
69
|
-
# Build verification
|
|
70
|
-
[ ] npm run build
|
|
57
|
+
npm test
|
|
58
|
+
npm run lint
|
|
59
|
+
npm run typecheck
|
|
60
|
+
```
|
|
71
61
|
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
**Python:**
|
|
63
|
+
```bash
|
|
64
|
+
pytest --tb=short
|
|
65
|
+
ruff check src/
|
|
66
|
+
mypy src/ --ignore-missing-imports
|
|
74
67
|
```
|
|
75
68
|
|
|
76
69
|
**Gate Strategy:**
|
|
77
70
|
- Fail fast on first error
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
- Option to skip non-critical gates with `--force`
|
|
81
|
-
|
|
82
|
-
### Phase 4: Commit & Cleanup
|
|
83
|
-
|
|
84
|
-
1. Stage all changes: `git add .`
|
|
85
|
-
2. Commit with message format:
|
|
86
|
-
```
|
|
87
|
-
feat: [task-id] - [description]
|
|
88
|
-
|
|
89
|
-
- Specific change 1
|
|
90
|
-
- Specific change 2
|
|
91
|
-
|
|
92
|
-
Fixes: [task-id]
|
|
93
|
-
```
|
|
94
|
-
3. Push to remote: `git push -u origin feature/[task-id]-[slug]`
|
|
95
|
-
4. Create PR if configured
|
|
96
|
-
5. Update `active.md`: mark complete with date
|
|
71
|
+
- Retry up to 2x with error context fed back to Claude
|
|
72
|
+
- Circuit breaker after 3 consecutive failures
|
|
97
73
|
|
|
98
|
-
### Phase
|
|
74
|
+
### Phase 4: Completion
|
|
99
75
|
|
|
100
|
-
1.
|
|
101
|
-
2.
|
|
102
|
-
3.
|
|
103
|
-
4. Continue to next
|
|
76
|
+
1. Mark story as `passes: true` in prd.json
|
|
77
|
+
2. Append learnings to `progress.txt`
|
|
78
|
+
3. Commit prd.json + progress.txt update
|
|
79
|
+
4. Continue to next story
|
|
104
80
|
|
|
105
81
|
## Batch Configuration
|
|
106
82
|
|
|
@@ -103,50 +103,10 @@ Create a session summary:
|
|
|
103
103
|
## Dead Ends
|
|
104
104
|
- DE-XXX: description (if any)
|
|
105
105
|
|
|
106
|
-
## Patterns Documented
|
|
107
|
-
- [{category}/{name}] - description (if any)
|
|
108
|
-
|
|
109
106
|
---
|
|
110
107
|
*Saved: {ISO timestamp}*
|
|
111
108
|
```
|
|
112
109
|
|
|
113
|
-
### 6. Pattern Extraction (.ai/patterns/)
|
|
114
|
-
|
|
115
|
-
Review the entire session and extract patterns. Check each criterion:
|
|
116
|
-
|
|
117
|
-
1. **User corrected your approach** → Document the correct approach
|
|
118
|
-
2. **You chose between alternatives** → Document what was chosen and why
|
|
119
|
-
3. **Workaround for limitation** → Document the workaround
|
|
120
|
-
4. **Convention established** → Document for consistency
|
|
121
|
-
5. **Repeated approach** → Document to avoid re-deriving
|
|
122
|
-
6. **Non-trivial solution** → Document for future sessions
|
|
123
|
-
|
|
124
|
-
**For each pattern found:**
|
|
125
|
-
|
|
126
|
-
1. Create file: `.ai/patterns/{category}/{pattern-name}.md`
|
|
127
|
-
```markdown
|
|
128
|
-
# Pattern Name
|
|
129
|
-
|
|
130
|
-
## Context
|
|
131
|
-
When to apply this pattern.
|
|
132
|
-
|
|
133
|
-
## Pattern
|
|
134
|
-
Description or code example.
|
|
135
|
-
|
|
136
|
-
## Rationale
|
|
137
|
-
Why this approach works better.
|
|
138
|
-
|
|
139
|
-
## Examples
|
|
140
|
-
- `src/path/file.ts:42`
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
2. Add index entry to `.ai/memory/patterns.md`:
|
|
144
|
-
```markdown
|
|
145
|
-
- [{category}/{name}](.ai/patterns/{category}/{name}.md) - Brief description
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
**If no patterns found:** Confirm "Patterns: no new patterns this session"
|
|
149
|
-
|
|
150
110
|
---
|
|
151
111
|
|
|
152
112
|
## Output Format
|
|
@@ -159,7 +119,6 @@ PWN Save Complete:
|
|
|
159
119
|
- Tasks: {X} active, {Y} completed
|
|
160
120
|
- Dead Ends: {new count or "no new"}
|
|
161
121
|
- Decisions: {new count or "no new"}
|
|
162
|
-
- Patterns: {new count or "no new"}
|
|
163
122
|
- Archive: .ai/memory/archive/{filename}
|
|
164
123
|
- Last updated: {timestamp}
|
|
165
124
|
```
|
|
@@ -173,7 +132,6 @@ PWN Save Complete:
|
|
|
173
132
|
3. **decisions.md**: Organized entries with proper DEC-XXX IDs
|
|
174
133
|
4. **active.md**: Only current sprint tasks
|
|
175
134
|
5. **archive/**: One file per session with summary
|
|
176
|
-
6. **patterns/**: One file per pattern, indexed in memory/patterns.md
|
|
177
135
|
|
|
178
136
|
---
|
|
179
137
|
|
package/cli/codespaces.js
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
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
|
-
}
|