apero-kit-cli 1.1.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apero-kit-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI tool to scaffold AI agent projects with pre-configured kits (Claude, OpenCode, Codex)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -66,6 +66,15 @@ Use `planner` subagent to:
66
66
  - Save the overview access point at `{plan-dir}/plan.md`. Keep it generic, under 80 lines, and list each implementation phase with status and progress plus links to phase files.
67
67
  - For each phase, create `{plan-dir}/phase-XX-phase-name-here.md` containing the following sections in order: Context links (reference parent plan, dependencies, docs), Overview (date, description, priority, implementation status, review status), Key Insights, Requirements, Architecture, Related code files, Implementation Steps, Todo list, Success Criteria, Risk Assessment, Security Considerations, Next steps.
68
68
 
69
+ ## Post-Plan Preview (Optional)
70
+
71
+ After plan creation, offer to open in browser for easier reading.
72
+
73
+ Use `AskUserQuestion` tool:
74
+ - "Open plan in browser for easier reading?" → Yes (Recommended) / No
75
+
76
+ **If user chooses Yes:** Run `/plan:preview {plan-path}` SlashCommand.
77
+
69
78
  ## Important Notes
70
79
 
71
80
  - **IMPORTANT:** Ensure token consumption efficiency while maintaining high quality.
@@ -45,6 +45,15 @@ After plan creation, offer validation interview to confirm decisions before impl
45
45
  **If mode is `prompt`:** Use `AskUserQuestion` tool with options above.
46
46
  **If user chooses validation or mode is `auto`:** Execute `/plan:validate {plan-path}` SlashCommand.
47
47
 
48
+ ## Post-Plan Preview (Optional)
49
+
50
+ After plan creation, offer to open in browser for easier reading.
51
+
52
+ Use `AskUserQuestion` tool:
53
+ - "Open plan in browser for easier reading?" → Yes (Recommended) / No
54
+
55
+ **If user chooses Yes:** Run `/plan:preview {plan-path}` SlashCommand.
56
+
48
57
  ## Output Requirements
49
58
 
50
59
  **Plan Directory Structure** (use `Plan dir:` from `## Naming` section)
@@ -131,6 +131,15 @@ Phase 04: Integration Tests (depends on 01, 02, 03)
131
131
  - Execution strategy (e.g., "Phases 1-3 parallel, then Phase 4")
132
132
  - File ownership matrix (which phase owns which files)
133
133
 
134
+ ## Post-Plan Preview (Optional)
135
+
136
+ After plan creation, offer to open in browser for easier reading.
137
+
138
+ Use `AskUserQuestion` tool:
139
+ - "Open plan in browser for easier reading?" → Yes (Recommended) / No
140
+
141
+ **If user chooses Yes:** Run `/plan:preview {plan-path}` SlashCommand.
142
+
134
143
  ## Important Notes
135
144
 
136
145
  **IMPORTANT:** Analyze the skills catalog and activate the skills that are needed for the task during the process.
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: 👁️ Open plan in browser for easy reading
3
+ argument-hint: [plan-path]
4
+ ---
5
+
6
+ ## Your mission
7
+
8
+ Open the plan preview server to view the plan in a nicely formatted web page.
9
+
10
+ ## Workflow
11
+
12
+ 1. **Determine plan path:**
13
+ - If `$ARGUMENTS` provided → Use that path
14
+ - If active plan exists in `## Plan Context` → Use that path
15
+ - If neither → Ask user to specify a plan path
16
+
17
+ 2. **Start preview server:**
18
+
19
+ ```bash
20
+ node .claude/scripts/plan-preview.cjs {plan-path}
21
+ ```
22
+
23
+ 3. **Inform user:**
24
+ - Browser will open automatically
25
+ - Server runs on `http://localhost:3456`
26
+ - Press `Ctrl+C` in terminal to stop
27
+
28
+ ## Example Usage
29
+
30
+ ```
31
+ /plan:preview plans/250116-feature-auth
32
+ /plan:preview # Uses active plan from context
33
+ ```
34
+
35
+ ## Notes
36
+
37
+ - The preview server renders markdown with syntax highlighting
38
+ - Navigation sidebar shows all plan files (plan.md, phases, reports)
39
+ - Server auto-detects plan structure (research/, reports/, scout/ subdirs)
40
+ - Refresh the page to see updated content
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Plan Preview Server
4
+ *
5
+ * Usage: node .claude/scripts/plan-preview.cjs <plan-path> [--port=3456]
6
+ *
7
+ * Opens a local web server to preview plans with:
8
+ * - Rendered markdown with syntax highlighting
9
+ * - Navigation sidebar for phases
10
+ * - Auto-refresh on file changes
11
+ */
12
+
13
+ const http = require('http');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { exec } = require('child_process');
17
+
18
+ const planPath = process.argv[2];
19
+ const portArg = process.argv.find(arg => arg.startsWith('--port='));
20
+ const PORT = portArg ? parseInt(portArg.split('=')[1]) : 3456;
21
+
22
+ if (!planPath) {
23
+ console.error('Error: Plan path required');
24
+ console.log('Usage: node .claude/scripts/plan-preview.cjs <plan-path>');
25
+ console.log('Example: node .claude/scripts/plan-preview.cjs plans/251207-feature');
26
+ process.exit(1);
27
+ }
28
+
29
+ const fullPlanPath = path.resolve(process.cwd(), planPath);
30
+ if (!fs.existsSync(fullPlanPath)) {
31
+ console.error(`Error: Plan directory not found: ${fullPlanPath}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ // Simple markdown to HTML converter
36
+ function markdownToHtml(md) {
37
+ return md
38
+ // Code blocks with language
39
+ .replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
40
+ const langClass = lang ? `language-${lang}` : '';
41
+ return `<pre class="${langClass}"><code>${escapeHtml(code.trim())}</code></pre>`;
42
+ })
43
+ // Inline code
44
+ .replace(/`([^`]+)`/g, '<code class="inline">$1</code>')
45
+ // Headers
46
+ .replace(/^### (.*$)/gm, '<h3>$1</h3>')
47
+ .replace(/^## (.*$)/gm, '<h2>$1</h2>')
48
+ .replace(/^# (.*$)/gm, '<h1>$1</h1>')
49
+ // Bold and italic
50
+ .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
51
+ .replace(/\*([^*]+)\*/g, '<em>$1</em>')
52
+ // Links
53
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
54
+ // Lists
55
+ .replace(/^\s*[-*] (.*$)/gm, '<li>$1</li>')
56
+ .replace(/(<li>.*<\/li>)\n(?!<li>)/g, '$1</ul>\n')
57
+ .replace(/(?<!<\/li>\n)(<li>)/g, '<ul>$1')
58
+ // Numbered lists
59
+ .replace(/^\s*\d+\. (.*$)/gm, '<li class="numbered">$1</li>')
60
+ // Blockquotes
61
+ .replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>')
62
+ // Horizontal rules
63
+ .replace(/^---+$/gm, '<hr>')
64
+ // Paragraphs
65
+ .replace(/\n\n/g, '</p><p>')
66
+ // Tables (simple)
67
+ .replace(/\|(.+)\|/g, (match) => {
68
+ const cells = match.split('|').filter(c => c.trim());
69
+ if (cells.every(c => /^[-:]+$/.test(c.trim()))) return ''; // Skip separator
70
+ const tag = 'td';
71
+ return '<tr>' + cells.map(c => `<${tag}>${c.trim()}</${tag}>`).join('') + '</tr>';
72
+ });
73
+ }
74
+
75
+ function escapeHtml(text) {
76
+ return text
77
+ .replace(/&/g, '&amp;')
78
+ .replace(/</g, '&lt;')
79
+ .replace(/>/g, '&gt;')
80
+ .replace(/"/g, '&quot;');
81
+ }
82
+
83
+ // Get all markdown files in plan directory
84
+ function getPlanFiles(dir) {
85
+ const files = [];
86
+ const items = fs.readdirSync(dir, { withFileTypes: true });
87
+
88
+ for (const item of items) {
89
+ if (item.isFile() && item.name.endsWith('.md')) {
90
+ files.push({
91
+ name: item.name,
92
+ path: path.join(dir, item.name),
93
+ isMain: item.name === 'plan.md',
94
+ isPhase: item.name.startsWith('phase-')
95
+ });
96
+ } else if (item.isDirectory()) {
97
+ // Include reports, research, scout subdirs
98
+ const subFiles = getPlanFiles(path.join(dir, item.name));
99
+ files.push(...subFiles.map(f => ({
100
+ ...f,
101
+ name: `${item.name}/${f.name}`,
102
+ category: item.name
103
+ })));
104
+ }
105
+ }
106
+
107
+ return files.sort((a, b) => {
108
+ if (a.isMain) return -1;
109
+ if (b.isMain) return 1;
110
+ if (a.isPhase && !b.isPhase) return -1;
111
+ if (!a.isPhase && b.isPhase) return 1;
112
+ return a.name.localeCompare(b.name);
113
+ });
114
+ }
115
+
116
+ // Generate HTML page
117
+ function generatePage(files, currentFile) {
118
+ const file = files.find(f => f.name === currentFile) || files[0];
119
+ const content = file ? fs.readFileSync(file.path, 'utf-8') : '# No plan found';
120
+ const htmlContent = markdownToHtml(content);
121
+
122
+ const nav = files.map(f => {
123
+ const isActive = f.name === (currentFile || files[0]?.name);
124
+ const icon = f.isMain ? '📋' : f.isPhase ? '📌' : f.category === 'research' ? '🔬' : f.category === 'reports' ? '📄' : '📝';
125
+ return `<a href="?file=${encodeURIComponent(f.name)}" class="${isActive ? 'active' : ''}">${icon} ${f.name}</a>`;
126
+ }).join('\n');
127
+
128
+ return `<!DOCTYPE html>
129
+ <html lang="en">
130
+ <head>
131
+ <meta charset="UTF-8">
132
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
133
+ <title>Plan Preview - ${path.basename(fullPlanPath)}</title>
134
+ <style>
135
+ :root {
136
+ --bg: #0d1117;
137
+ --bg-secondary: #161b22;
138
+ --text: #c9d1d9;
139
+ --text-muted: #8b949e;
140
+ --accent: #58a6ff;
141
+ --border: #30363d;
142
+ --code-bg: #1f2428;
143
+ --success: #3fb950;
144
+ --warning: #d29922;
145
+ }
146
+
147
+ * { box-sizing: border-box; margin: 0; padding: 0; }
148
+
149
+ body {
150
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
151
+ background: var(--bg);
152
+ color: var(--text);
153
+ line-height: 1.6;
154
+ display: flex;
155
+ min-height: 100vh;
156
+ }
157
+
158
+ /* Sidebar */
159
+ .sidebar {
160
+ width: 280px;
161
+ background: var(--bg-secondary);
162
+ border-right: 1px solid var(--border);
163
+ padding: 20px;
164
+ position: fixed;
165
+ height: 100vh;
166
+ overflow-y: auto;
167
+ }
168
+
169
+ .sidebar h2 {
170
+ color: var(--text);
171
+ font-size: 14px;
172
+ text-transform: uppercase;
173
+ letter-spacing: 0.5px;
174
+ margin-bottom: 16px;
175
+ padding-bottom: 8px;
176
+ border-bottom: 1px solid var(--border);
177
+ }
178
+
179
+ .sidebar a {
180
+ display: block;
181
+ color: var(--text-muted);
182
+ text-decoration: none;
183
+ padding: 8px 12px;
184
+ border-radius: 6px;
185
+ font-size: 13px;
186
+ margin-bottom: 4px;
187
+ transition: all 0.2s;
188
+ }
189
+
190
+ .sidebar a:hover {
191
+ background: var(--border);
192
+ color: var(--text);
193
+ }
194
+
195
+ .sidebar a.active {
196
+ background: rgba(88, 166, 255, 0.15);
197
+ color: var(--accent);
198
+ }
199
+
200
+ /* Main content */
201
+ .main {
202
+ flex: 1;
203
+ margin-left: 280px;
204
+ padding: 40px 60px;
205
+ max-width: 900px;
206
+ }
207
+
208
+ .main h1 {
209
+ font-size: 32px;
210
+ margin-bottom: 24px;
211
+ padding-bottom: 16px;
212
+ border-bottom: 1px solid var(--border);
213
+ }
214
+
215
+ .main h2 {
216
+ font-size: 24px;
217
+ margin: 32px 0 16px;
218
+ color: var(--text);
219
+ }
220
+
221
+ .main h3 {
222
+ font-size: 18px;
223
+ margin: 24px 0 12px;
224
+ color: var(--text);
225
+ }
226
+
227
+ .main p {
228
+ margin-bottom: 16px;
229
+ }
230
+
231
+ .main ul, .main ol {
232
+ margin: 16px 0;
233
+ padding-left: 24px;
234
+ }
235
+
236
+ .main li {
237
+ margin-bottom: 8px;
238
+ }
239
+
240
+ .main a {
241
+ color: var(--accent);
242
+ }
243
+
244
+ .main blockquote {
245
+ border-left: 4px solid var(--accent);
246
+ padding-left: 16px;
247
+ margin: 16px 0;
248
+ color: var(--text-muted);
249
+ }
250
+
251
+ .main hr {
252
+ border: none;
253
+ border-top: 1px solid var(--border);
254
+ margin: 32px 0;
255
+ }
256
+
257
+ /* Code */
258
+ pre {
259
+ background: var(--code-bg);
260
+ padding: 16px;
261
+ border-radius: 8px;
262
+ overflow-x: auto;
263
+ margin: 16px 0;
264
+ border: 1px solid var(--border);
265
+ }
266
+
267
+ code {
268
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
269
+ font-size: 13px;
270
+ }
271
+
272
+ code.inline {
273
+ background: var(--code-bg);
274
+ padding: 2px 6px;
275
+ border-radius: 4px;
276
+ color: var(--accent);
277
+ }
278
+
279
+ /* Tables */
280
+ table {
281
+ width: 100%;
282
+ border-collapse: collapse;
283
+ margin: 16px 0;
284
+ }
285
+
286
+ th, td {
287
+ padding: 12px;
288
+ border: 1px solid var(--border);
289
+ text-align: left;
290
+ }
291
+
292
+ th {
293
+ background: var(--bg-secondary);
294
+ }
295
+
296
+ /* Status badges */
297
+ .status-pending { color: var(--warning); }
298
+ .status-completed { color: var(--success); }
299
+
300
+ /* Footer */
301
+ .footer {
302
+ margin-top: 48px;
303
+ padding-top: 24px;
304
+ border-top: 1px solid var(--border);
305
+ color: var(--text-muted);
306
+ font-size: 12px;
307
+ }
308
+
309
+ /* Responsive */
310
+ @media (max-width: 768px) {
311
+ .sidebar { width: 100%; height: auto; position: relative; }
312
+ .main { margin-left: 0; padding: 20px; }
313
+ body { flex-direction: column; }
314
+ }
315
+ </style>
316
+ </head>
317
+ <body>
318
+ <nav class="sidebar">
319
+ <h2>📁 ${path.basename(fullPlanPath)}</h2>
320
+ ${nav}
321
+ </nav>
322
+ <main class="main">
323
+ <article>
324
+ ${htmlContent}
325
+ </article>
326
+ <footer class="footer">
327
+ <p>Plan Preview Server • <a href="javascript:location.reload()">↻ Refresh</a></p>
328
+ </footer>
329
+ </main>
330
+ <script>
331
+ // Auto-refresh every 5 seconds (optional)
332
+ // setTimeout(() => location.reload(), 5000);
333
+ </script>
334
+ </body>
335
+ </html>`;
336
+ }
337
+
338
+ // Create HTTP server
339
+ const server = http.createServer((req, res) => {
340
+ const url = new URL(req.url, `http://localhost:${PORT}`);
341
+ const currentFile = url.searchParams.get('file');
342
+
343
+ const files = getPlanFiles(fullPlanPath);
344
+ const html = generatePage(files, currentFile);
345
+
346
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
347
+ res.end(html);
348
+ });
349
+
350
+ server.listen(PORT, () => {
351
+ const url = `http://localhost:${PORT}`;
352
+ console.log(`\n📋 Plan Preview Server`);
353
+ console.log(` Plan: ${planPath}`);
354
+ console.log(` URL: ${url}`);
355
+ console.log(`\n Press Ctrl+C to stop\n`);
356
+
357
+ // Open browser
358
+ const openCommand = process.platform === 'darwin' ? 'open' :
359
+ process.platform === 'win32' ? 'start' : 'xdg-open';
360
+ exec(`${openCommand} ${url}`);
361
+ });
362
+
363
+ // Handle shutdown
364
+ process.on('SIGINT', () => {
365
+ console.log('\n👋 Server stopped');
366
+ process.exit(0);
367
+ });