@deepguide-ai/dg
Version:
Self-testing CLI documentation tool that generates interactive terminal demos
166 lines (164 loc) ⢠6.35 kB
JavaScript
import * as p from '@clack/prompts';
import { join } from 'path';
import { existsSync, writeFileSync, mkdirSync } from 'fs';
import { readConfig, addCast, updateCast } from '../lib/config.js';
import { checkTermSVGAvailability, recordInteractiveDemo } from '../lib/termsvg.js';
import { detectInteractiveElements } from '../lib/interactive-detection.js';
import { generateSVG } from '../lib/svg-generation.js';
import { initLogger, dgLogger as logger } from '../lib/logger.js';
import { spawn } from 'child_process';
function slugify(text) {
return text
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}
export async function captureCommand(options = {}) {
// Initialize logger with options
initLogger({
verbose: options.verbose,
dev: options.dev
});
logger.debug('Starting capture command', { options });
// console.clear();
p.note('š¬ Record a new CLI demo', 'š¬');
// Check if DG is initialized
const config = readConfig();
if (!config) {
logger.error('No config found');
p.cancel('No config found. Run `dg init` first.');
return;
}
const termsvgCheck = await checkTermSVGAvailability();
if (!termsvgCheck.available) {
logger.error('termsvg not available', { version: termsvgCheck.version });
p.cancel('termsvgCheck is required for recording. Install it first');
return;
}
logger.debug('termsvg check passed', { version: termsvgCheck.version });
// Only ask for title
const title = await p.text({
message: 'Enter demo title:',
placeholder: 'e.g. Show Help, Init Project, etc.',
validate: (value) => {
if (!value || value.trim().length === 0) {
return 'Title is required';
}
}
});
if (p.isCancel(title)) {
logger.debug('User cancelled recording');
p.cancel('Recording cancelled.');
process.exit(0);
}
// Generate cast name
const castName = slugify(title);
const castPath = join(config.outputDir, 'casts', `${castName}.cast`);
logger.debug('Cast configuration', {
name: castName,
path: castPath
});
// Check for file conflicts
const fileExists = existsSync(castPath);
const shouldOverwrite = options.overwrite || fileExists;
if (shouldOverwrite && fileExists) {
logger.debug('Found existing cast file, will overwrite', { path: castPath });
}
// Show recording instructions and start
p.note(`Title: ${title}\n\n⢠Run any commands you want to demonstrate\n⢠Commands are executed in your current shell\n⢠Press Ctrl+D when you're finished`, 'ā¶ļø Recording started - run your commands now');
let lastCommand;
const recordSuccess = await recordInteractiveDemo(castPath, {
overwrite: shouldOverwrite,
onCommand: (cmd) => {
lastCommand = cmd;
}
});
if (!recordSuccess) {
logger.error('Recording failed');
p.cancel('Recording failed. Check that termsvg is working properly.');
return;
}
// Quietly check for interactive elements
const hasInteractive = await detectInteractiveElements(castPath);
logger.debug('Interactive elements check', { hasInteractive, path: castPath });
// Create or update cast configuration
const cast = {
name: castName,
title: title,
created: new Date().toISOString(),
updated: new Date().toISOString(),
interactive: hasInteractive,
validation: hasInteractive
? { mode: 'manual-only' }
: { mode: 'auto' },
command: hasInteractive ? undefined : lastCommand
};
try {
if (shouldOverwrite) {
updateCast(cast);
}
else {
addCast(cast);
}
logger.debug('Cast configuration saved', { cast });
}
catch (error) {
logger.error('Failed to save cast configuration', {
error: error.message,
stack: error.stack,
cast
});
p.cancel('Failed to save cast configuration: ' + error.message);
return;
}
// Generate assets and save markdown
try {
// Generate SVGs for both themes
const svgOptions = {
width: 120,
height: 30,
minify: true
};
const lightResult = await generateSVG(castPath, 'light', svgOptions);
const darkResult = await generateSVG(castPath, 'dark', svgOptions);
if (lightResult.warning || darkResult.warning) {
logger.warn('SVG generation warning', {
light: lightResult.warning,
dark: darkResult.warning
});
}
// Generate simple documentation snippet with both themes
const documentation = `## ${cast.title}
<!--Remove one image if your site handles dark-mode automatically-->


<!-- Self-testing badge (remove if not using CI yet) -->

`;
// Save snippet to file and copy to clipboard
const snippetsDir = join(config.outputDir, 'snippets');
if (!existsSync(snippetsDir)) {
mkdirSync(snippetsDir, { recursive: true });
}
const snippetPath = join(snippetsDir, `${castName}.md`);
writeFileSync(snippetPath, documentation, 'utf8');
// Copy to clipboard
const clipboardCmd = process.platform === 'darwin'
? 'pbcopy'
: process.platform === 'win32'
? 'clip.exe'
: 'xclip -selection clipboard';
const copyProcess = spawn(clipboardCmd, [], { shell: true });
copyProcess.stdin.write(documentation);
copyProcess.stdin.end();
p.outro(`š Markdown snippet saved to .dg/snippets/${castName}.md and copied to clipboard š`);
}
catch (error) {
logger.error(error);
p.cancel('Failed to generate assets. Check the logs for details.');
process.exit(1);
}
}
//# sourceMappingURL=capture.js.map