apero-kit-cli 1.4.2 → 1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apero-kit-cli",
3
- "version": "1.4.2",
3
+ "version": "1.7.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": {
@@ -1,11 +1,5 @@
1
- import http from 'http';
2
- import { join } from 'path';
3
1
  import { exec } from 'child_process';
4
- import fs from 'fs-extra';
5
2
  import chalk from 'chalk';
6
- import { CLI_ROOT, TEMPLATES_DIR, resolveSource } from '../utils/paths.js';
7
-
8
- const PORT = 3457;
9
3
 
10
4
  // Bilingual content
11
5
  const i18n = {
@@ -1403,38 +1397,16 @@ function generateWorkflowsSection(t, lang) {
1403
1397
  `;
1404
1398
  }
1405
1399
 
1400
+ const HELP_URL = 'https://www.vividkit.dev/vi/guides/what-is-claudekit';
1401
+
1406
1402
  /**
1407
- * Help command - open browser with interactive documentation
1403
+ * Help command - open VividKit documentation in browser
1408
1404
  */
1409
1405
  export async function helpCommand(options) {
1410
- const source = resolveSource(options.source);
1411
-
1412
- console.log(chalk.cyan('\nšŸ“š Starting help server...\n'));
1413
-
1414
- const server = http.createServer((req, res) => {
1415
- const url = new URL(req.url, `http://localhost:${PORT}`);
1416
- const section = url.searchParams.get('section') || 'overview';
1417
- const lang = url.searchParams.get('lang') || 'vi';
1418
-
1419
- const html = generateHelpPage(section, lang, source);
1420
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
1421
- res.end(html);
1422
- });
1423
-
1424
- server.listen(PORT, () => {
1425
- const url = `http://localhost:${PORT}`;
1426
- console.log(chalk.green(` Help server running at: ${url}`));
1427
- console.log(chalk.gray(' Press Ctrl+C to stop\n'));
1428
-
1429
- // Open browser
1430
- const openCommand = process.platform === 'darwin' ? 'open' :
1431
- process.platform === 'win32' ? 'start' : 'xdg-open';
1432
- exec(`${openCommand} ${url}`);
1433
- });
1434
-
1435
- // Handle shutdown
1436
- process.on('SIGINT', () => {
1437
- console.log(chalk.yellow('\nšŸ‘‹ Help server stopped'));
1438
- process.exit(0);
1439
- });
1406
+ console.log(chalk.cyan('\nšŸ“š Opening VividKit documentation...\n'));
1407
+ console.log(chalk.green(` ${HELP_URL}\n`));
1408
+
1409
+ const openCommand = process.platform === 'darwin' ? 'open' :
1410
+ process.platform === 'win32' ? 'start' : 'xdg-open';
1411
+ exec(`${openCommand} ${HELP_URL}`);
1440
1412
  }
@@ -1,7 +1,8 @@
1
1
  import { fileURLToPath } from 'url';
2
2
  import { dirname, join, resolve } from 'path';
3
- import { existsSync, statSync } from 'fs';
3
+ import { existsSync, statSync, mkdirSync } from 'fs';
4
4
  import { execSync } from 'child_process';
5
+ import { homedir } from 'os';
5
6
 
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = dirname(__filename);
@@ -12,6 +13,10 @@ export const CLI_ROOT = resolve(__dirname, '../..');
12
13
  // Embedded templates directory (inside CLI package)
13
14
  export const TEMPLATES_DIR = join(CLI_ROOT, 'templates');
14
15
 
16
+ // Remote templates config
17
+ const REMOTE_REPO_URL = 'https://github.com/Thanhnguyen6702/CK-Internal.git';
18
+ const CACHE_DIR = join(homedir(), '.apero-kit', 'CK-Internal');
19
+
15
20
  // Target folder mappings
16
21
  export const TARGETS = {
17
22
  claude: '.claude',
@@ -19,6 +24,55 @@ export const TARGETS = {
19
24
  generic: '.agent'
20
25
  };
21
26
 
27
+ /**
28
+ * Fetch or update remote templates from CK-Internal GitHub repo.
29
+ * Clones on first run, pulls on subsequent runs.
30
+ * Returns source object or null on failure.
31
+ */
32
+ export function fetchRemoteTemplates() {
33
+ try {
34
+ const cacheParent = join(homedir(), '.apero-kit');
35
+ if (!existsSync(cacheParent)) {
36
+ mkdirSync(cacheParent, { recursive: true });
37
+ }
38
+
39
+ if (existsSync(join(CACHE_DIR, '.git'))) {
40
+ // Already cloned — pull latest
41
+ try {
42
+ execSync('git pull --ff-only', {
43
+ cwd: CACHE_DIR,
44
+ encoding: 'utf-8',
45
+ stdio: ['pipe', 'pipe', 'pipe'],
46
+ timeout: 30000
47
+ });
48
+ } catch {
49
+ // pull failed (offline, etc.) — use cached version
50
+ }
51
+ } else {
52
+ // First time — clone
53
+ execSync(`git clone --depth 1 "${REMOTE_REPO_URL}" "${CACHE_DIR}"`, {
54
+ encoding: 'utf-8',
55
+ stdio: ['pipe', 'pipe', 'pipe'],
56
+ timeout: 60000
57
+ });
58
+ }
59
+
60
+ const claudeDir = join(CACHE_DIR, '.claude');
61
+ if (existsSync(claudeDir) && statSync(claudeDir).isDirectory()) {
62
+ const agentsMd = join(CACHE_DIR, 'AGENTS.md');
63
+ return {
64
+ path: CACHE_DIR,
65
+ type: 'remote',
66
+ claudeDir,
67
+ agentsMd: existsSync(agentsMd) ? agentsMd : null
68
+ };
69
+ }
70
+ } catch {
71
+ // Clone failed (no network, etc.) — fall through
72
+ }
73
+ return null;
74
+ }
75
+
22
76
  /**
23
77
  * Get embedded templates (bundled with CLI)
24
78
  */
@@ -177,19 +231,25 @@ export function resolveSource(sourceFlag) {
177
231
  return { error: `No .claude/ or .opencode/ found in: ${sourceFlag}` };
178
232
  }
179
233
 
180
- // 2. Use embedded templates (bundled with CLI) - PREFERRED
234
+ // 2. Fetch from remote CK-Internal repo - PREFERRED
235
+ const remote = fetchRemoteTemplates();
236
+ if (remote) {
237
+ return remote;
238
+ }
239
+
240
+ // 3. Use embedded templates (bundled with CLI) - FALLBACK
181
241
  const embedded = getEmbeddedTemplates();
182
242
  if (embedded) {
183
243
  return embedded;
184
244
  }
185
245
 
186
- // 3. Fallback: auto-detect in parent directories
246
+ // 4. Fallback: auto-detect in parent directories
187
247
  const found = findSource();
188
248
  if (found) {
189
249
  return found;
190
250
  }
191
251
 
192
252
  return {
193
- error: 'No templates found. The CLI package may be corrupted. Try reinstalling: npm install -g apero-kit-cli'
253
+ error: 'No templates found. Check your network connection or try reinstalling: npm install -g apero-kit-cli'
194
254
  };
195
255
  }
@@ -33,44 +33,13 @@ if (!fs.existsSync(fullPlanPath)) {
33
33
  process.exit(1);
34
34
  }
35
35
 
36
- // Simple markdown to HTML converter
37
- function markdownToHtml(md) {
38
- return md
39
- // Code blocks with language
40
- .replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
41
- const langClass = lang ? `language-${lang}` : '';
42
- return `<pre class="${langClass}"><code>${escapeHtml(code.trim())}</code></pre>`;
43
- })
44
- // Inline code
45
- .replace(/`([^`]+)`/g, '<code class="inline">$1</code>')
46
- // Headers
47
- .replace(/^### (.*$)/gm, '<h3>$1</h3>')
48
- .replace(/^## (.*$)/gm, '<h2>$1</h2>')
49
- .replace(/^# (.*$)/gm, '<h1>$1</h1>')
50
- // Bold and italic
51
- .replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
52
- .replace(/\*([^*]+)\*/g, '<em>$1</em>')
53
- // Links
54
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
55
- // Lists
56
- .replace(/^\s*[-*] (.*$)/gm, '<li>$1</li>')
57
- .replace(/(<li>.*<\/li>)\n(?!<li>)/g, '$1</ul>\n')
58
- .replace(/(?<!<\/li>\n)(<li>)/g, '<ul>$1')
59
- // Numbered lists
60
- .replace(/^\s*\d+\. (.*$)/gm, '<li class="numbered">$1</li>')
61
- // Blockquotes
62
- .replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>')
63
- // Horizontal rules
64
- .replace(/^---+$/gm, '<hr>')
65
- // Paragraphs
66
- .replace(/\n\n/g, '</p><p>')
67
- // Tables (simple)
68
- .replace(/\|(.+)\|/g, (match) => {
69
- const cells = match.split('|').filter(c => c.trim());
70
- if (cells.every(c => /^[-:]+$/.test(c.trim()))) return ''; // Skip separator
71
- const tag = 'td';
72
- return '<tr>' + cells.map(c => `<${tag}>${c.trim()}</${tag}>`).join('') + '</tr>';
73
- });
36
+ // Markdown to HTML - Uses marked.js on client-side for full GFM support
37
+ // Server just passes raw markdown, client renders with marked.js
38
+ function markdownToHtml(md, forEditor = false) {
39
+ // For editor preview pane, we'll render client-side with marked.js
40
+ // For static preview, we embed raw markdown and render on page load
41
+ const encodedMd = encodeURIComponent(md);
42
+ return `<div class="markdown-body" data-markdown="${encodedMd}"><noscript>${escapeHtml(md)}</noscript><div class="loading">Loading...</div></div>`;
74
43
  }
75
44
 
76
45
  function escapeHtml(text) {
@@ -134,6 +103,22 @@ function generatePage(files, currentFile, mode = 'preview') {
134
103
  <meta charset="UTF-8">
135
104
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
136
105
  <title>Plan Preview - ${path.basename(fullPlanPath)}</title>
106
+
107
+ <!-- marked.js - Markdown Parser -->
108
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
109
+
110
+ <!-- Prism.js - Syntax Highlighting -->
111
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet" />
112
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
113
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
114
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js"></script>
115
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
116
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
117
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
118
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
119
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js"></script>
120
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js"></script>
121
+
137
122
  <style>
138
123
  :root {
139
124
  --bg: #0d1117;
@@ -467,6 +452,171 @@ function generatePage(files, currentFile, mode = 'preview') {
467
452
  justify-content: space-between;
468
453
  }
469
454
 
455
+ /* GitHub-style Markdown Body */
456
+ .markdown-body {
457
+ line-height: 1.7;
458
+ }
459
+
460
+ .markdown-body .loading {
461
+ color: var(--text-muted);
462
+ font-style: italic;
463
+ }
464
+
465
+ .markdown-body h1 {
466
+ font-size: 2em;
467
+ border-bottom: 1px solid var(--border);
468
+ padding-bottom: 0.3em;
469
+ margin: 24px 0 16px;
470
+ }
471
+
472
+ .markdown-body h2 {
473
+ font-size: 1.5em;
474
+ border-bottom: 1px solid var(--border);
475
+ padding-bottom: 0.3em;
476
+ margin: 24px 0 16px;
477
+ }
478
+
479
+ .markdown-body h3 {
480
+ font-size: 1.25em;
481
+ margin: 24px 0 16px;
482
+ }
483
+
484
+ .markdown-body h4 {
485
+ font-size: 1em;
486
+ margin: 24px 0 16px;
487
+ }
488
+
489
+ .markdown-body p {
490
+ margin: 0 0 16px;
491
+ }
492
+
493
+ .markdown-body ul, .markdown-body ol {
494
+ margin: 0 0 16px;
495
+ padding-left: 2em;
496
+ }
497
+
498
+ .markdown-body li {
499
+ margin: 4px 0;
500
+ }
501
+
502
+ .markdown-body li > p {
503
+ margin: 0;
504
+ }
505
+
506
+ /* Task Lists */
507
+ .markdown-body input[type="checkbox"] {
508
+ margin-right: 8px;
509
+ vertical-align: middle;
510
+ }
511
+
512
+ .markdown-body li.task-list-item {
513
+ list-style: none;
514
+ margin-left: -1.5em;
515
+ }
516
+
517
+ /* Tables - GitHub style */
518
+ .markdown-body table {
519
+ width: 100%;
520
+ border-collapse: collapse;
521
+ margin: 16px 0;
522
+ display: block;
523
+ overflow-x: auto;
524
+ }
525
+
526
+ .markdown-body thead {
527
+ background: var(--bg-tertiary);
528
+ }
529
+
530
+ .markdown-body th, .markdown-body td {
531
+ padding: 12px 16px;
532
+ border: 1px solid var(--border);
533
+ text-align: left;
534
+ }
535
+
536
+ .markdown-body th {
537
+ font-weight: 600;
538
+ }
539
+
540
+ .markdown-body tbody tr:nth-child(even) {
541
+ background: var(--bg-secondary);
542
+ }
543
+
544
+ .markdown-body tbody tr:hover {
545
+ background: rgba(88, 166, 255, 0.05);
546
+ }
547
+
548
+ /* Code blocks with Prism */
549
+ .markdown-body pre {
550
+ background: var(--code-bg);
551
+ border: 1px solid var(--border);
552
+ border-radius: 8px;
553
+ padding: 16px;
554
+ overflow-x: auto;
555
+ margin: 16px 0;
556
+ }
557
+
558
+ .markdown-body pre code {
559
+ background: none;
560
+ padding: 0;
561
+ border-radius: 0;
562
+ font-size: 13px;
563
+ color: var(--text);
564
+ }
565
+
566
+ .markdown-body code {
567
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
568
+ font-size: 85%;
569
+ background: var(--code-bg);
570
+ padding: 0.2em 0.4em;
571
+ border-radius: 4px;
572
+ color: var(--accent);
573
+ }
574
+
575
+ /* Blockquotes */
576
+ .markdown-body blockquote {
577
+ border-left: 4px solid var(--accent);
578
+ padding: 0 16px;
579
+ margin: 16px 0;
580
+ color: var(--text-muted);
581
+ }
582
+
583
+ .markdown-body blockquote > :first-child {
584
+ margin-top: 0;
585
+ }
586
+
587
+ .markdown-body blockquote > :last-child {
588
+ margin-bottom: 0;
589
+ }
590
+
591
+ /* Images */
592
+ .markdown-body img {
593
+ max-width: 100%;
594
+ height: auto;
595
+ border-radius: 8px;
596
+ }
597
+
598
+ /* Horizontal Rule */
599
+ .markdown-body hr {
600
+ border: none;
601
+ border-top: 2px solid var(--border);
602
+ margin: 32px 0;
603
+ }
604
+
605
+ /* Links */
606
+ .markdown-body a {
607
+ color: var(--accent);
608
+ text-decoration: none;
609
+ }
610
+
611
+ .markdown-body a:hover {
612
+ text-decoration: underline;
613
+ }
614
+
615
+ /* Strikethrough */
616
+ .markdown-body del {
617
+ color: var(--text-muted);
618
+ }
619
+
470
620
  /* Responsive */
471
621
  @media (max-width: 768px) {
472
622
  .sidebar { width: 100%; height: auto; position: relative; }
@@ -476,6 +626,16 @@ function generatePage(files, currentFile, mode = 'preview') {
476
626
  .editor { min-height: 300px; }
477
627
  .preview-pane { min-height: 300px; }
478
628
  }
629
+
630
+ /* Print styles */
631
+ @media print {
632
+ .sidebar, .toolbar, .mode-toggle, .footer { display: none !important; }
633
+ .main { margin-left: 0; padding: 20px; }
634
+ body { background: white; color: black; }
635
+ .markdown-body pre { border: 1px solid #ddd; background: #f5f5f5; }
636
+ .markdown-body code { background: #f5f5f5; color: #333; }
637
+ .markdown-body a { color: #0366d6; }
638
+ }
479
639
  </style>
480
640
  </head>
481
641
  <body>
@@ -535,6 +695,36 @@ function generatePage(files, currentFile, mode = 'preview') {
535
695
 
536
696
  originalContent = editor.value;
537
697
 
698
+ // Configure marked.js for GitHub Flavored Markdown
699
+ marked.setOptions({
700
+ gfm: true,
701
+ breaks: true,
702
+ headerIds: true,
703
+ mangle: false
704
+ });
705
+
706
+ // Custom renderer for syntax highlighting with Prism
707
+ const renderer = new marked.Renderer();
708
+ renderer.code = function(code, language) {
709
+ const lang = language || '';
710
+ const validLang = Prism.languages[lang] ? lang : 'plaintext';
711
+ let highlighted;
712
+ try {
713
+ highlighted = Prism.languages[validLang]
714
+ ? Prism.highlight(code, Prism.languages[validLang], validLang)
715
+ : code;
716
+ } catch (e) {
717
+ highlighted = code;
718
+ }
719
+ return '<pre class="language-' + validLang + '"><code class="language-' + validLang + '">' + highlighted + '</code></pre>';
720
+ };
721
+ marked.setOptions({ renderer });
722
+
723
+ // Render markdown with marked.js
724
+ function renderMarkdown(md) {
725
+ return marked.parse(md);
726
+ }
727
+
538
728
  // Live preview update
539
729
  let updateTimeout;
540
730
  editor.addEventListener('input', () => {
@@ -543,26 +733,15 @@ function generatePage(files, currentFile, mode = 'preview') {
543
733
 
544
734
  clearTimeout(updateTimeout);
545
735
  updateTimeout = setTimeout(() => {
546
- preview.innerHTML = simpleMarkdown(editor.value);
547
- }, 300);
736
+ preview.innerHTML = renderMarkdown(editor.value);
737
+ // Re-run Prism highlighting for any code blocks
738
+ Prism.highlightAllUnder(preview);
739
+ }, 150);
548
740
  });
549
741
 
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
- }
742
+ // Initial render
743
+ preview.innerHTML = renderMarkdown(editor.value);
744
+ Prism.highlightAllUnder(preview);
566
745
 
567
746
  // Update status indicator
568
747
  function updateStatus() {
@@ -632,7 +811,42 @@ function generatePage(files, currentFile, mode = 'preview') {
632
811
  e.returnValue = '';
633
812
  }
634
813
  });
635
- ` : ''}
814
+ ` : `
815
+ // Preview mode: Render markdown on page load
816
+ document.addEventListener('DOMContentLoaded', () => {
817
+ // Configure marked.js
818
+ marked.setOptions({
819
+ gfm: true,
820
+ breaks: true,
821
+ headerIds: true,
822
+ mangle: false
823
+ });
824
+
825
+ // Custom renderer for syntax highlighting with Prism
826
+ const renderer = new marked.Renderer();
827
+ renderer.code = function(code, language) {
828
+ const lang = language || '';
829
+ const validLang = Prism.languages[lang] ? lang : 'plaintext';
830
+ let highlighted;
831
+ try {
832
+ highlighted = Prism.languages[validLang]
833
+ ? Prism.highlight(code, Prism.languages[validLang], validLang)
834
+ : code;
835
+ } catch (e) {
836
+ highlighted = code;
837
+ }
838
+ return '<pre class="language-' + validLang + '"><code class="language-' + validLang + '">' + highlighted + '</code></pre>';
839
+ };
840
+ marked.setOptions({ renderer });
841
+
842
+ // Find all markdown bodies and render them
843
+ document.querySelectorAll('.markdown-body[data-markdown]').forEach(el => {
844
+ const rawMd = decodeURIComponent(el.dataset.markdown);
845
+ el.innerHTML = marked.parse(rawMd);
846
+ Prism.highlightAllUnder(el);
847
+ });
848
+ });
849
+ `}
636
850
  </script>
637
851
  </body>
638
852
  </html>`;