@fermindi/pwn-cli 0.3.0 → 0.3.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/cli/inject.js +3 -1
- package/cli/update.js +43 -2
- package/package.json +1 -1
- package/src/core/inject.js +28 -0
- package/templates/workspace/.claude/commands/save.md +142 -0
package/cli/inject.js
CHANGED
|
@@ -65,7 +65,9 @@ export default async function injectCommand(args = []) {
|
|
|
65
65
|
console.log(' ├── patterns/ (auto-applied patterns)');
|
|
66
66
|
console.log(' ├── workflows/ (batch execution)');
|
|
67
67
|
console.log(' ├── agents/ (AI agent configs)');
|
|
68
|
-
console.log(' └── config/ (notifications, etc)
|
|
68
|
+
console.log(' └── config/ (notifications, etc)');
|
|
69
|
+
console.log(' .claude/');
|
|
70
|
+
console.log(' └── commands/ (slash commands: /save)\n');
|
|
69
71
|
|
|
70
72
|
// Show ntfy topic if generated
|
|
71
73
|
const notifyPath = join(process.cwd(), '.ai', 'config', 'notifications.json');
|
package/cli/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync, cpSync, renameSync } from 'fs';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, cpSync, renameSync, mkdirSync, readdirSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
|
|
@@ -21,6 +21,13 @@ const FRAMEWORK_FILES = [
|
|
|
21
21
|
'README.md',
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Claude Code slash commands (in .claude/commands/)
|
|
26
|
+
*/
|
|
27
|
+
const CLAUDE_COMMANDS = [
|
|
28
|
+
'save.md',
|
|
29
|
+
];
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* Files that should NOT be updated (user data)
|
|
26
33
|
* These contain user customizations
|
|
@@ -148,6 +155,39 @@ export default async function updateCommand(args = []) {
|
|
|
148
155
|
}
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
// Update .claude/commands/ (slash commands)
|
|
159
|
+
const claudeCommandsDir = join(cwd, '.claude', 'commands');
|
|
160
|
+
const templateCommandsDir = join(__dirname, '../templates/workspace/.claude/commands');
|
|
161
|
+
|
|
162
|
+
if (existsSync(templateCommandsDir)) {
|
|
163
|
+
for (const cmdFile of CLAUDE_COMMANDS) {
|
|
164
|
+
const templateFile = join(templateCommandsDir, cmdFile);
|
|
165
|
+
const targetFile = join(claudeCommandsDir, cmdFile);
|
|
166
|
+
|
|
167
|
+
if (!existsSync(templateFile)) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const templateContent = readFileSync(templateFile, 'utf8');
|
|
172
|
+
const targetExists = existsSync(targetFile);
|
|
173
|
+
const targetContent = targetExists ? readFileSync(targetFile, 'utf8') : '';
|
|
174
|
+
|
|
175
|
+
if (templateContent !== targetContent) {
|
|
176
|
+
if (dryRun) {
|
|
177
|
+
console.log(` 📝 Would update: .claude/commands/${cmdFile}`);
|
|
178
|
+
} else {
|
|
179
|
+
// Ensure directory exists
|
|
180
|
+
if (!existsSync(claudeCommandsDir)) {
|
|
181
|
+
mkdirSync(claudeCommandsDir, { recursive: true });
|
|
182
|
+
}
|
|
183
|
+
writeFileSync(targetFile, templateContent);
|
|
184
|
+
console.log(` 📝 Updated: .claude/commands/${cmdFile}`);
|
|
185
|
+
}
|
|
186
|
+
updated.push(`.claude/commands/${cmdFile}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
151
191
|
// Update state.json with new version
|
|
152
192
|
if (!dryRun && existsSync(statePath)) {
|
|
153
193
|
try {
|
|
@@ -185,5 +225,6 @@ export default async function updateCommand(args = []) {
|
|
|
185
225
|
console.log('🔒 Preserved (user data):');
|
|
186
226
|
console.log(' .ai/memory/ (decisions, patterns, deadends)');
|
|
187
227
|
console.log(' .ai/tasks/ (active, backlog)');
|
|
188
|
-
console.log(' .ai/state.json (session state)
|
|
228
|
+
console.log(' .ai/state.json (session state)');
|
|
229
|
+
console.log(' .claude/ (your custom commands preserved)\n');
|
|
189
230
|
}
|
package/package.json
CHANGED
package/src/core/inject.js
CHANGED
|
@@ -86,6 +86,14 @@ export function getTemplatePath() {
|
|
|
86
86
|
return join(__dirname, '../../templates/workspace/.ai');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Get the path to the .claude commands template
|
|
91
|
+
* @returns {string} Path to .claude template directory
|
|
92
|
+
*/
|
|
93
|
+
export function getClaudeCommandsTemplatePath() {
|
|
94
|
+
return join(__dirname, '../../templates/workspace/.claude');
|
|
95
|
+
}
|
|
96
|
+
|
|
89
97
|
/**
|
|
90
98
|
* Inject PWN workspace into a project
|
|
91
99
|
* @param {object} options - Injection options
|
|
@@ -104,7 +112,9 @@ export async function inject(options = {}) {
|
|
|
104
112
|
} = options;
|
|
105
113
|
|
|
106
114
|
const templateDir = getTemplatePath();
|
|
115
|
+
const claudeTemplateDir = getClaudeCommandsTemplatePath();
|
|
107
116
|
const targetDir = join(cwd, '.ai');
|
|
117
|
+
const claudeDir = join(cwd, '.claude');
|
|
108
118
|
|
|
109
119
|
const log = silent ? () => {} : console.log;
|
|
110
120
|
|
|
@@ -193,6 +203,24 @@ export async function inject(options = {}) {
|
|
|
193
203
|
}
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
// Copy .claude/commands/ for slash commands
|
|
207
|
+
if (existsSync(claudeTemplateDir)) {
|
|
208
|
+
// Create .claude/commands/ if it doesn't exist
|
|
209
|
+
const commandsDir = join(claudeDir, 'commands');
|
|
210
|
+
if (!existsSync(commandsDir)) {
|
|
211
|
+
mkdirSync(commandsDir, { recursive: true });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Copy all command files
|
|
215
|
+
const claudeCommandsSource = join(claudeTemplateDir, 'commands');
|
|
216
|
+
if (existsSync(claudeCommandsSource)) {
|
|
217
|
+
cpSync(claudeCommandsSource, commandsDir, { recursive: true });
|
|
218
|
+
if (!silent) {
|
|
219
|
+
console.log('📝 Created .claude/commands/ with PWN slash commands');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
196
224
|
// Backup other AI files (not CLAUDE.md) to .ai/
|
|
197
225
|
const otherFiles = Object.fromEntries(
|
|
198
226
|
Object.entries(backedUpContent).filter(([k]) => k.toLowerCase() !== 'claude.md')
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# /save - PWN Session Save
|
|
2
|
+
|
|
3
|
+
Save your current work state to PWN framework files (.ai/state.json and .ai/memory/).
|
|
4
|
+
|
|
5
|
+
## What this command does
|
|
6
|
+
|
|
7
|
+
1. Updates .ai/state.json with current session state
|
|
8
|
+
2. Updates .ai/tasks/active.md with task progress
|
|
9
|
+
3. Logs new dead ends to .ai/memory/deadends.md
|
|
10
|
+
4. Optionally updates .ai/memory/decisions.md with new decisions
|
|
11
|
+
5. Creates archive entry in .ai/memory/archive/
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Instructions
|
|
16
|
+
|
|
17
|
+
First, identify the current developer by running:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git config user.name
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then, analyze the current session and update PWN files accordingly.
|
|
24
|
+
|
|
25
|
+
## Files to Update
|
|
26
|
+
|
|
27
|
+
### 1. State File (.ai/state.json)
|
|
28
|
+
|
|
29
|
+
Update the session state:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"developer": "{username from git config}",
|
|
34
|
+
"session_started": "{ISO timestamp}",
|
|
35
|
+
"session_mode": "interactive",
|
|
36
|
+
"current_task": "{task ID or null}",
|
|
37
|
+
"context_loaded": ["memory", "patterns", "tasks"],
|
|
38
|
+
"last_save": "{current ISO timestamp}"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Active Tasks (.ai/tasks/active.md)
|
|
43
|
+
|
|
44
|
+
If there are tasks in progress:
|
|
45
|
+
- Update checkbox status ([ ] or [x])
|
|
46
|
+
- Add notes about blockers
|
|
47
|
+
- Update priority if changed
|
|
48
|
+
|
|
49
|
+
### 3. Dead Ends (.ai/memory/deadends.md)
|
|
50
|
+
|
|
51
|
+
When a new dead end is discovered:
|
|
52
|
+
|
|
53
|
+
1. **Get next ID** from existing entries (last DE-XXX + 1)
|
|
54
|
+
|
|
55
|
+
2. **Add detailed entry**:
|
|
56
|
+
```markdown
|
|
57
|
+
## DE-XXX: Title
|
|
58
|
+
|
|
59
|
+
**Date:** YYYY-MM-DD
|
|
60
|
+
**Attempted:** What was tried
|
|
61
|
+
**Problem:** Why it failed
|
|
62
|
+
**Solution:** What worked instead
|
|
63
|
+
**Tags:** relevant, keywords
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Decisions (.ai/memory/decisions.md)
|
|
67
|
+
|
|
68
|
+
If new architectural decisions were made:
|
|
69
|
+
|
|
70
|
+
1. **Get next ID** from existing entries (last DEC-XXX + 1)
|
|
71
|
+
|
|
72
|
+
2. **Add entry**:
|
|
73
|
+
```markdown
|
|
74
|
+
## DEC-XXX: Decision Title
|
|
75
|
+
|
|
76
|
+
**Date:** YYYY-MM-DD
|
|
77
|
+
**Status:** Active
|
|
78
|
+
**Context:** Why this decision was needed
|
|
79
|
+
**Decision:** What was decided
|
|
80
|
+
**Rationale:** Why this is the best choice
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 5. Archive Entry (.ai/memory/archive/{date}-session.md)
|
|
84
|
+
|
|
85
|
+
Create a session summary:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
# Session: YYYY-MM-DD
|
|
89
|
+
|
|
90
|
+
## Summary
|
|
91
|
+
{brief description of what was done this session}
|
|
92
|
+
|
|
93
|
+
## Tasks
|
|
94
|
+
### Completed
|
|
95
|
+
- [x] Task descriptions...
|
|
96
|
+
|
|
97
|
+
### In Progress
|
|
98
|
+
- [ ] Task descriptions...
|
|
99
|
+
|
|
100
|
+
## Decisions Made
|
|
101
|
+
- DEC-XXX: description (if any)
|
|
102
|
+
|
|
103
|
+
## Dead Ends
|
|
104
|
+
- DE-XXX: description (if any)
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
*Saved: {ISO timestamp}*
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Output Format
|
|
113
|
+
|
|
114
|
+
After updating all files, confirm to the user:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
PWN Save Complete:
|
|
118
|
+
- State: .ai/state.json updated (developer: {username})
|
|
119
|
+
- Tasks: {X} active, {Y} completed
|
|
120
|
+
- Dead Ends: {new count or "no new"}
|
|
121
|
+
- Decisions: {new count or "no new"}
|
|
122
|
+
- Archive: .ai/memory/archive/{filename}
|
|
123
|
+
- Last updated: {timestamp}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Rules
|
|
129
|
+
|
|
130
|
+
1. **state.json**: Only current session state, no history
|
|
131
|
+
2. **deadends.md**: Detailed entries with proper DE-XXX IDs
|
|
132
|
+
3. **decisions.md**: Organized entries with proper DEC-XXX IDs
|
|
133
|
+
4. **active.md**: Only current sprint tasks
|
|
134
|
+
5. **archive/**: One file per session with summary
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Notes
|
|
139
|
+
|
|
140
|
+
- This command replaces the old /checkpoint command
|
|
141
|
+
- Uses PWN framework structure in .ai/ directory
|
|
142
|
+
- Always get the next available ID number before adding entries
|