apero-kit-cli 1.4.0 ā 1.4.2
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/bin/ak.js +1 -1
- package/package.json +1 -1
- package/src/commands/help.js +686 -286
- package/templates/scripts/plan-preview.cjs +357 -17
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Plan Preview Server
|
|
3
|
+
* Plan Preview Server with Edit Support
|
|
4
4
|
*
|
|
5
5
|
* Usage: node .claude/scripts/plan-preview.cjs <plan-path> [--port=3456]
|
|
6
6
|
*
|
|
7
|
-
* Opens a local web server to preview plans with:
|
|
7
|
+
* Opens a local web server to preview and edit plans with:
|
|
8
8
|
* - Rendered markdown with syntax highlighting
|
|
9
9
|
* - Navigation sidebar for phases
|
|
10
|
-
* -
|
|
10
|
+
* - Edit mode with live preview
|
|
11
|
+
* - Auto-save functionality
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const http = require('http');
|
|
@@ -113,16 +114,18 @@ function getPlanFiles(dir) {
|
|
|
113
114
|
});
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
// Generate HTML page
|
|
117
|
-
function generatePage(files, currentFile) {
|
|
117
|
+
// Generate HTML page with edit support
|
|
118
|
+
function generatePage(files, currentFile, mode = 'preview') {
|
|
118
119
|
const file = files.find(f => f.name === currentFile) || files[0];
|
|
119
120
|
const content = file ? fs.readFileSync(file.path, 'utf-8') : '# No plan found';
|
|
120
121
|
const htmlContent = markdownToHtml(content);
|
|
122
|
+
const isEditMode = mode === 'edit';
|
|
123
|
+
const currentFileName = currentFile || files[0]?.name || '';
|
|
121
124
|
|
|
122
125
|
const nav = files.map(f => {
|
|
123
126
|
const isActive = f.name === (currentFile || files[0]?.name);
|
|
124
127
|
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>`;
|
|
128
|
+
return `<a href="?file=${encodeURIComponent(f.name)}&mode=${mode}" class="${isActive ? 'active' : ''}">${icon} ${f.name}</a>`;
|
|
126
129
|
}).join('\n');
|
|
127
130
|
|
|
128
131
|
return `<!DOCTYPE html>
|
|
@@ -135,6 +138,7 @@ function generatePage(files, currentFile) {
|
|
|
135
138
|
:root {
|
|
136
139
|
--bg: #0d1117;
|
|
137
140
|
--bg-secondary: #161b22;
|
|
141
|
+
--bg-tertiary: #21262d;
|
|
138
142
|
--text: #c9d1d9;
|
|
139
143
|
--text-muted: #8b949e;
|
|
140
144
|
--accent: #58a6ff;
|
|
@@ -142,6 +146,9 @@ function generatePage(files, currentFile) {
|
|
|
142
146
|
--code-bg: #1f2428;
|
|
143
147
|
--success: #3fb950;
|
|
144
148
|
--warning: #d29922;
|
|
149
|
+
--error: #f85149;
|
|
150
|
+
--gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
151
|
+
--gradient-2: linear-gradient(135deg, #3fb950 0%, #2ea043 100%);
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -197,12 +204,48 @@ function generatePage(files, currentFile) {
|
|
|
197
204
|
color: var(--accent);
|
|
198
205
|
}
|
|
199
206
|
|
|
207
|
+
/* Mode Toggle */
|
|
208
|
+
.mode-toggle {
|
|
209
|
+
display: flex;
|
|
210
|
+
background: var(--bg-tertiary);
|
|
211
|
+
border-radius: 20px;
|
|
212
|
+
padding: 4px;
|
|
213
|
+
margin-bottom: 20px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.mode-toggle a {
|
|
217
|
+
flex: 1;
|
|
218
|
+
text-align: center;
|
|
219
|
+
padding: 8px 16px;
|
|
220
|
+
border-radius: 16px;
|
|
221
|
+
font-size: 13px;
|
|
222
|
+
font-weight: 500;
|
|
223
|
+
transition: all 0.3s ease;
|
|
224
|
+
margin: 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.mode-toggle a.active {
|
|
228
|
+
background: var(--gradient-1);
|
|
229
|
+
color: white;
|
|
230
|
+
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.mode-toggle a:not(.active) {
|
|
234
|
+
background: transparent;
|
|
235
|
+
color: var(--text-muted);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.mode-toggle a:not(.active):hover {
|
|
239
|
+
color: var(--text);
|
|
240
|
+
background: var(--border);
|
|
241
|
+
}
|
|
242
|
+
|
|
200
243
|
/* Main content */
|
|
201
244
|
.main {
|
|
202
245
|
flex: 1;
|
|
203
246
|
margin-left: 280px;
|
|
204
247
|
padding: 40px 60px;
|
|
205
|
-
max-width:
|
|
248
|
+
max-width: 100%;
|
|
206
249
|
}
|
|
207
250
|
|
|
208
251
|
.main h1 {
|
|
@@ -293,9 +336,125 @@ function generatePage(files, currentFile) {
|
|
|
293
336
|
background: var(--bg-secondary);
|
|
294
337
|
}
|
|
295
338
|
|
|
296
|
-
/*
|
|
297
|
-
.
|
|
298
|
-
|
|
339
|
+
/* Editor */
|
|
340
|
+
.editor-container {
|
|
341
|
+
display: ${isEditMode ? 'flex' : 'none'};
|
|
342
|
+
gap: 20px;
|
|
343
|
+
height: calc(100vh - 200px);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.editor-pane {
|
|
347
|
+
flex: 1;
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.editor-pane h3 {
|
|
353
|
+
margin: 0 0 12px 0;
|
|
354
|
+
font-size: 14px;
|
|
355
|
+
color: var(--text-muted);
|
|
356
|
+
text-transform: uppercase;
|
|
357
|
+
letter-spacing: 0.5px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.editor {
|
|
361
|
+
flex: 1;
|
|
362
|
+
width: 100%;
|
|
363
|
+
background: var(--code-bg);
|
|
364
|
+
border: 1px solid var(--border);
|
|
365
|
+
border-radius: 8px;
|
|
366
|
+
padding: 16px;
|
|
367
|
+
color: var(--text);
|
|
368
|
+
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
369
|
+
font-size: 14px;
|
|
370
|
+
line-height: 1.6;
|
|
371
|
+
resize: none;
|
|
372
|
+
outline: none;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.editor:focus {
|
|
376
|
+
border-color: var(--accent);
|
|
377
|
+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.preview-pane {
|
|
381
|
+
flex: 1;
|
|
382
|
+
background: var(--bg-secondary);
|
|
383
|
+
border: 1px solid var(--border);
|
|
384
|
+
border-radius: 8px;
|
|
385
|
+
padding: 20px;
|
|
386
|
+
overflow-y: auto;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.preview-content {
|
|
390
|
+
display: ${isEditMode ? 'none' : 'block'};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/* Toolbar */
|
|
394
|
+
.toolbar {
|
|
395
|
+
display: flex;
|
|
396
|
+
gap: 10px;
|
|
397
|
+
margin-bottom: 20px;
|
|
398
|
+
align-items: center;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.save-btn {
|
|
402
|
+
background: var(--gradient-2);
|
|
403
|
+
color: white;
|
|
404
|
+
border: none;
|
|
405
|
+
padding: 10px 24px;
|
|
406
|
+
border-radius: 8px;
|
|
407
|
+
font-size: 14px;
|
|
408
|
+
font-weight: 500;
|
|
409
|
+
cursor: pointer;
|
|
410
|
+
transition: all 0.2s;
|
|
411
|
+
display: flex;
|
|
412
|
+
align-items: center;
|
|
413
|
+
gap: 8px;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.save-btn:hover {
|
|
417
|
+
transform: translateY(-1px);
|
|
418
|
+
box-shadow: 0 4px 12px rgba(63, 185, 80, 0.3);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.save-btn:active {
|
|
422
|
+
transform: translateY(0);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.save-btn:disabled {
|
|
426
|
+
opacity: 0.5;
|
|
427
|
+
cursor: not-allowed;
|
|
428
|
+
transform: none;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.status {
|
|
432
|
+
font-size: 13px;
|
|
433
|
+
color: var(--text-muted);
|
|
434
|
+
margin-left: auto;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.status.saved {
|
|
438
|
+
color: var(--success);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.status.saving {
|
|
442
|
+
color: var(--warning);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.status.error {
|
|
446
|
+
color: var(--error);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/* Keyboard shortcut hint */
|
|
450
|
+
.shortcut {
|
|
451
|
+
font-size: 11px;
|
|
452
|
+
color: var(--text-muted);
|
|
453
|
+
background: var(--bg-tertiary);
|
|
454
|
+
padding: 2px 6px;
|
|
455
|
+
border-radius: 4px;
|
|
456
|
+
margin-left: 8px;
|
|
457
|
+
}
|
|
299
458
|
|
|
300
459
|
/* Footer */
|
|
301
460
|
.footer {
|
|
@@ -304,6 +463,8 @@ function generatePage(files, currentFile) {
|
|
|
304
463
|
border-top: 1px solid var(--border);
|
|
305
464
|
color: var(--text-muted);
|
|
306
465
|
font-size: 12px;
|
|
466
|
+
display: flex;
|
|
467
|
+
justify-content: space-between;
|
|
307
468
|
}
|
|
308
469
|
|
|
309
470
|
/* Responsive */
|
|
@@ -311,37 +472,212 @@ function generatePage(files, currentFile) {
|
|
|
311
472
|
.sidebar { width: 100%; height: auto; position: relative; }
|
|
312
473
|
.main { margin-left: 0; padding: 20px; }
|
|
313
474
|
body { flex-direction: column; }
|
|
475
|
+
.editor-container { flex-direction: column; height: auto; }
|
|
476
|
+
.editor { min-height: 300px; }
|
|
477
|
+
.preview-pane { min-height: 300px; }
|
|
314
478
|
}
|
|
315
479
|
</style>
|
|
316
480
|
</head>
|
|
317
481
|
<body>
|
|
318
482
|
<nav class="sidebar">
|
|
319
483
|
<h2>š ${path.basename(fullPlanPath)}</h2>
|
|
484
|
+
|
|
485
|
+
<div class="mode-toggle">
|
|
486
|
+
<a href="?file=${encodeURIComponent(currentFileName)}&mode=preview" class="${!isEditMode ? 'active' : ''}">šļø Preview</a>
|
|
487
|
+
<a href="?file=${encodeURIComponent(currentFileName)}&mode=edit" class="${isEditMode ? 'active' : ''}">āļø Edit</a>
|
|
488
|
+
</div>
|
|
489
|
+
|
|
320
490
|
${nav}
|
|
321
491
|
</nav>
|
|
492
|
+
|
|
322
493
|
<main class="main">
|
|
323
|
-
|
|
494
|
+
${isEditMode ? `
|
|
495
|
+
<div class="toolbar">
|
|
496
|
+
<button class="save-btn" onclick="saveFile()" id="saveBtn">
|
|
497
|
+
š¾ Save
|
|
498
|
+
<span class="shortcut">āS</span>
|
|
499
|
+
</button>
|
|
500
|
+
<span class="status" id="status">Ready to edit</span>
|
|
501
|
+
</div>
|
|
502
|
+
|
|
503
|
+
<div class="editor-container">
|
|
504
|
+
<div class="editor-pane">
|
|
505
|
+
<h3>š Markdown Editor</h3>
|
|
506
|
+
<textarea class="editor" id="editor" spellcheck="false">${escapeHtml(content)}</textarea>
|
|
507
|
+
</div>
|
|
508
|
+
<div class="editor-pane">
|
|
509
|
+
<h3>šļø Live Preview</h3>
|
|
510
|
+
<div class="preview-pane" id="preview">${htmlContent}</div>
|
|
511
|
+
</div>
|
|
512
|
+
</div>
|
|
513
|
+
` : `
|
|
514
|
+
<article class="preview-content">
|
|
324
515
|
${htmlContent}
|
|
325
516
|
</article>
|
|
517
|
+
`}
|
|
518
|
+
|
|
326
519
|
<footer class="footer">
|
|
327
|
-
<
|
|
520
|
+
<span>Plan Preview Server ⢠Press <kbd>Ctrl+C</kbd> in terminal to stop</span>
|
|
521
|
+
<span><a href="javascript:location.reload()">ā» Refresh</a></span>
|
|
328
522
|
</footer>
|
|
329
523
|
</main>
|
|
524
|
+
|
|
330
525
|
<script>
|
|
331
|
-
|
|
332
|
-
|
|
526
|
+
const currentFile = '${escapeHtml(currentFileName)}';
|
|
527
|
+
let originalContent = '';
|
|
528
|
+
let hasChanges = false;
|
|
529
|
+
|
|
530
|
+
${isEditMode ? `
|
|
531
|
+
const editor = document.getElementById('editor');
|
|
532
|
+
const preview = document.getElementById('preview');
|
|
533
|
+
const status = document.getElementById('status');
|
|
534
|
+
const saveBtn = document.getElementById('saveBtn');
|
|
535
|
+
|
|
536
|
+
originalContent = editor.value;
|
|
537
|
+
|
|
538
|
+
// Live preview update
|
|
539
|
+
let updateTimeout;
|
|
540
|
+
editor.addEventListener('input', () => {
|
|
541
|
+
hasChanges = editor.value !== originalContent;
|
|
542
|
+
updateStatus();
|
|
543
|
+
|
|
544
|
+
clearTimeout(updateTimeout);
|
|
545
|
+
updateTimeout = setTimeout(() => {
|
|
546
|
+
preview.innerHTML = simpleMarkdown(editor.value);
|
|
547
|
+
}, 300);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
// Simple client-side markdown (for live preview)
|
|
551
|
+
function simpleMarkdown(md) {
|
|
552
|
+
return md
|
|
553
|
+
.replace(/\`\`\`(\\w+)?\\n([\\s\\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>')
|
|
554
|
+
.replace(/\`([^\`]+)\`/g, '<code class="inline">$1</code>')
|
|
555
|
+
.replace(/^### (.*$)/gm, '<h3>$1</h3>')
|
|
556
|
+
.replace(/^## (.*$)/gm, '<h2>$1</h2>')
|
|
557
|
+
.replace(/^# (.*$)/gm, '<h1>$1</h1>')
|
|
558
|
+
.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')
|
|
559
|
+
.replace(/\\*([^*]+)\\*/g, '<em>$1</em>')
|
|
560
|
+
.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>')
|
|
561
|
+
.replace(/^\\s*[-*] (.*$)/gm, '<li>$1</li>')
|
|
562
|
+
.replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>')
|
|
563
|
+
.replace(/^---+$/gm, '<hr>')
|
|
564
|
+
.replace(/\\n\\n/g, '</p><p>');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Update status indicator
|
|
568
|
+
function updateStatus() {
|
|
569
|
+
if (hasChanges) {
|
|
570
|
+
status.textContent = 'ā Unsaved changes';
|
|
571
|
+
status.className = 'status';
|
|
572
|
+
} else {
|
|
573
|
+
status.textContent = 'ā Saved';
|
|
574
|
+
status.className = 'status saved';
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Save file
|
|
579
|
+
async function saveFile() {
|
|
580
|
+
if (!hasChanges) return;
|
|
581
|
+
|
|
582
|
+
saveBtn.disabled = true;
|
|
583
|
+
status.textContent = 'Saving...';
|
|
584
|
+
status.className = 'status saving';
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
const response = await fetch('/save', {
|
|
588
|
+
method: 'POST',
|
|
589
|
+
headers: { 'Content-Type': 'application/json' },
|
|
590
|
+
body: JSON.stringify({
|
|
591
|
+
file: currentFile,
|
|
592
|
+
content: editor.value
|
|
593
|
+
})
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
const result = await response.json();
|
|
597
|
+
|
|
598
|
+
if (result.success) {
|
|
599
|
+
originalContent = editor.value;
|
|
600
|
+
hasChanges = false;
|
|
601
|
+
status.textContent = 'ā Saved successfully';
|
|
602
|
+
status.className = 'status saved';
|
|
603
|
+
|
|
604
|
+
setTimeout(() => {
|
|
605
|
+
if (!hasChanges) {
|
|
606
|
+
status.textContent = 'ā Saved';
|
|
607
|
+
}
|
|
608
|
+
}, 2000);
|
|
609
|
+
} else {
|
|
610
|
+
throw new Error(result.error || 'Save failed');
|
|
611
|
+
}
|
|
612
|
+
} catch (err) {
|
|
613
|
+
status.textContent = 'ā ' + err.message;
|
|
614
|
+
status.className = 'status error';
|
|
615
|
+
} finally {
|
|
616
|
+
saveBtn.disabled = false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Keyboard shortcut: Cmd/Ctrl + S to save
|
|
621
|
+
document.addEventListener('keydown', (e) => {
|
|
622
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
623
|
+
e.preventDefault();
|
|
624
|
+
saveFile();
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Warn before leaving with unsaved changes
|
|
629
|
+
window.addEventListener('beforeunload', (e) => {
|
|
630
|
+
if (hasChanges) {
|
|
631
|
+
e.preventDefault();
|
|
632
|
+
e.returnValue = '';
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
` : ''}
|
|
333
636
|
</script>
|
|
334
637
|
</body>
|
|
335
638
|
</html>`;
|
|
336
639
|
}
|
|
337
640
|
|
|
338
|
-
// Create HTTP server
|
|
641
|
+
// Create HTTP server with save endpoint
|
|
339
642
|
const server = http.createServer((req, res) => {
|
|
340
643
|
const url = new URL(req.url, `http://localhost:${PORT}`);
|
|
644
|
+
|
|
645
|
+
// Handle save endpoint
|
|
646
|
+
if (req.method === 'POST' && url.pathname === '/save') {
|
|
647
|
+
let body = '';
|
|
648
|
+
req.on('data', chunk => { body += chunk; });
|
|
649
|
+
req.on('end', () => {
|
|
650
|
+
try {
|
|
651
|
+
const { file, content } = JSON.parse(body);
|
|
652
|
+
const files = getPlanFiles(fullPlanPath);
|
|
653
|
+
const targetFile = files.find(f => f.name === file);
|
|
654
|
+
|
|
655
|
+
if (!targetFile) {
|
|
656
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
657
|
+
res.end(JSON.stringify({ success: false, error: 'File not found' }));
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Write file
|
|
662
|
+
fs.writeFileSync(targetFile.path, content, 'utf-8');
|
|
663
|
+
console.log(`š¾ Saved: ${file}`);
|
|
664
|
+
|
|
665
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
666
|
+
res.end(JSON.stringify({ success: true }));
|
|
667
|
+
} catch (err) {
|
|
668
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
669
|
+
res.end(JSON.stringify({ success: false, error: err.message }));
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Handle GET requests
|
|
341
676
|
const currentFile = url.searchParams.get('file');
|
|
677
|
+
const mode = url.searchParams.get('mode') || 'preview';
|
|
342
678
|
|
|
343
679
|
const files = getPlanFiles(fullPlanPath);
|
|
344
|
-
const html = generatePage(files, currentFile);
|
|
680
|
+
const html = generatePage(files, currentFile, mode);
|
|
345
681
|
|
|
346
682
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
347
683
|
res.end(html);
|
|
@@ -349,9 +685,13 @@ const server = http.createServer((req, res) => {
|
|
|
349
685
|
|
|
350
686
|
server.listen(PORT, () => {
|
|
351
687
|
const url = `http://localhost:${PORT}`;
|
|
352
|
-
console.log(`\nš Plan Preview Server`);
|
|
688
|
+
console.log(`\nš Plan Preview Server (with Edit Support)`);
|
|
353
689
|
console.log(` Plan: ${planPath}`);
|
|
354
690
|
console.log(` URL: ${url}`);
|
|
691
|
+
console.log(`\n Features:`);
|
|
692
|
+
console.log(` ⢠Preview mode: View rendered markdown`);
|
|
693
|
+
console.log(` ⢠Edit mode: Edit with live preview`);
|
|
694
|
+
console.log(` ⢠Save: Cmd/Ctrl+S or click Save button`);
|
|
355
695
|
console.log(`\n Press Ctrl+C to stop\n`);
|
|
356
696
|
|
|
357
697
|
// Open browser
|