ccshare
Version:
Share Claude Code prompts and results easily
300 lines ⢠12.5 kB
JavaScript
import { Command } from 'commander';
import chalk from 'chalk';
import inquirer from 'inquirer';
import ora from 'ora';
import { captureSession, captureRawSession } from './capture.js';
import { uploadSession } from './upload.js';
import { fetchFromSlug } from './share-service.js';
import { createAutoPostForm } from './browser-post.js';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
async function openUrl(url) {
const platform = process.platform;
let command;
if (platform === 'darwin') {
command = `open "${url}"`;
}
else if (platform === 'win32') {
command = `start "${url}"`;
}
else {
command = `xdg-open "${url}"`;
}
try {
await execAsync(command);
}
catch (error) {
console.error('Failed to open URL:', error);
}
}
const program = new Command();
program
.name('ccshare')
.description('Share Claude Code prompts and results')
.version('0.1.0')
.option('-s, --session <path>', 'Path to session file or directory')
.option('-a, --all', 'Include all session history')
.option('-n, --no-select', 'Skip prompt selection')
.option('-r, --recent <number>', 'Include only the N most recent prompts', parseInt)
.option('--html', 'Generate HTML report instead of sharing')
.option('--api-url <url>', 'Custom API URL for sharing', 'https://ccshare.cc/shares')
.option('--json', 'Output JSON format instead of HTML')
.option('--exclude-auto', 'Exclude auto-generated prompts')
.option('-l, --limit <number>', 'Maximum number of prompts to fetch from session files', parseInt)
.action(async (options) => {
// Default action - share raw session data to API
try {
// Use limit from options, recent flag, or default to 20
const limit = options.limit || options.recent || (options.all ? 1000 : 20);
const spinner = options.json ? null : ora('Fetching session data...').start();
const rawData = await captureRawSession(options.session, limit);
if (spinner)
spinner.succeed('Session data fetched');
// Allow user to select prompts
let selectedPrompts = rawData.prompts;
// If --recent is used, skip prompt selection
if (options.recent) {
selectedPrompts = rawData.prompts.slice(-options.recent);
if (!options.json) {
console.log(chalk.cyan(`\nš Using ${options.recent} most recent prompts`));
}
}
else if (options.select !== false && !options.json && rawData.prompts.length > 0) {
// Reverse the prompts to show most recent first
const reversedPrompts = [...rawData.prompts].reverse();
const choices = reversedPrompts.map((p, index) => {
// Replace newlines and multiple spaces with single space
const cleanContent = p.userPrompt.message?.content
?.replace(/\n+/g, ' ')
?.replace(/\s+/g, ' ')
?.trim() || '';
const preview = cleanContent.substring(0, 100);
return {
name: `${reversedPrompts.length - index}. ${preview}${cleanContent.length > 100 ? '...' : ''}`,
value: rawData.prompts.length - 1 - index, // Map back to original index
checked: true
};
});
const { selected } = await inquirer.prompt([{
type: 'checkbox',
name: 'selected',
message: 'Select prompts to share (most recent first):',
choices,
pageSize: 15
}]);
selectedPrompts = selected.map((idx) => rawData.prompts[idx]);
if (selectedPrompts.length === 0) {
console.log(chalk.yellow('\nNo prompts selected.'));
process.exit(0);
}
}
// Flatten selected prompts into raw session entries array
const sessionEntries = [];
selectedPrompts.forEach(prompt => {
// Add user prompt
sessionEntries.push(prompt.userPrompt);
// Add all subsequent entries
sessionEntries.push(...prompt.sessionEntries);
});
// Add ccshare metadata
const metadata = {
...rawData.metadata,
ccshareVersion: '0.3.0',
generatedAt: new Date().toISOString(),
selectedPromptsCount: selectedPrompts.length,
totalEntriesCount: sessionEntries.length
};
const payload = {
sessionEntries,
metadata
};
if (options.json) {
console.log(JSON.stringify(payload, null, 2));
process.exit(0);
}
// HTML generation is now deprecated
if (options.html) {
console.log(chalk.yellow('ā ļø HTML generation is deprecated. Use --json to see the raw data.'));
process.exit(0);
}
// Send to API via browser form submission
const apiUrl = options.apiUrl;
// Show JSON if requested (for debugging)
if (process.env.DEBUG_SHARE) {
console.log('\nJSON being sent to API:');
console.log(JSON.stringify(payload, null, 2));
}
// Always use browser form submission
const tempFilePath = await createAutoPostForm(payload, apiUrl);
if (!options.json) {
console.log(chalk.cyan('š¤ Opening browser to share...'));
}
await openUrl(`file://${tempFilePath}`);
}
catch (error) {
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('share')
.description('Capture and share current Claude Code session')
.option('-f, --file <path>', 'Path to Claude Code session file')
.option('-m, --message <message>', 'Optional message to include with share')
.action(async (options) => {
try {
console.log(chalk.blue('š CCShare - Claude Code Session Sharing Tool'));
const spinner = ora('Capturing Claude Code session...').start();
let sessionData;
if (options.file) {
sessionData = await captureSession(options.file);
}
else {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'sessionPath',
message: 'Enter path to Claude Code session file (or press enter to use default):',
default: process.env.HOME + '/.claude/sessions/latest.json'
}
]);
sessionData = await captureSession(answers.sessionPath);
}
spinner.succeed('Session captured successfully');
if (!options.message) {
const messageAnswer = await inquirer.prompt([
{
type: 'input',
name: 'message',
message: 'Add a description for this share (optional):'
}
]);
sessionData.message = messageAnswer.message;
}
else {
sessionData.message = options.message;
}
spinner.start('Uploading session to ccshare...');
const shareUrl = await uploadSession(sessionData);
spinner.succeed('Session uploaded successfully');
console.log(chalk.green('\nā
Your session has been shared!'));
console.log(chalk.white('Share URL: ') + chalk.cyan(shareUrl));
console.log(chalk.gray('\nAnyone with this link can view your Claude Code session.'));
}
catch (error) {
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('load <slug>')
.description('Load prompts from a shared slug and execute with claude -p')
.option('--api-url <url>', 'Custom API URL', 'https://ccshare.cc/shares')
.option('--dry-run', 'Show prompts without executing')
.option('--select', 'Select which prompts to execute')
.action(async (slug, options) => {
try {
const spinner = ora(`Fetching share: ${slug}...`).start();
const shareData = await fetchFromSlug(slug, options.apiUrl);
if (!shareData) {
spinner.fail('Failed to fetch share');
process.exit(1);
}
spinner.succeed(`Fetched ${shareData.prompts.length} prompts`);
// Extract user prompts
const userPrompts = shareData.prompts.filter(p => !p.isAutoGenerated);
if (userPrompts.length === 0) {
console.log(chalk.yellow('No user prompts found in this share.'));
process.exit(0);
}
// Select prompts if requested
let selectedPrompts = userPrompts;
if (options.select) {
const promptChoices = userPrompts.map((prompt, index) => {
const preview = prompt.content.substring(0, 80).replace(/\n/g, ' ');
return {
name: `${index + 1}. ${preview}${prompt.content.length > 80 ? '...' : ''}`,
value: index,
checked: true
};
});
const { selected } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selected',
message: 'Select prompts to execute:',
choices: promptChoices,
pageSize: 15
}
]);
selectedPrompts = userPrompts.filter((_, index) => selected.includes(index));
}
if (selectedPrompts.length === 0) {
console.log(chalk.yellow('No prompts selected.'));
process.exit(0);
}
// Show what will be executed
console.log(chalk.cyan(`\nWill execute ${selectedPrompts.length} prompts:`));
selectedPrompts.forEach((prompt, index) => {
const preview = prompt.content.substring(0, 100).replace(/\n/g, ' ');
console.log(chalk.white(`${index + 1}. ${preview}${prompt.content.length > 100 ? '...' : ''}`));
});
if (options.dryRun) {
console.log(chalk.gray('\n[Dry run mode - not executing]'));
process.exit(0);
}
// Execute each prompt with claude -p
console.log(chalk.cyan('\nExecuting prompts...\n'));
for (let i = 0; i < selectedPrompts.length; i++) {
const prompt = selectedPrompts[i];
console.log(chalk.blue(`[${i + 1}/${selectedPrompts.length}] Executing prompt...`));
try {
// Escape quotes in the prompt content
const escapedPrompt = prompt.content.replace(/'/g, "'\\''");
// Execute claude -p command
const { stdout, stderr } = await execAsync(`claude -p '${escapedPrompt}'`, {
maxBuffer: 1024 * 1024 * 10 // 10MB buffer
});
if (stdout) {
console.log(stdout);
}
if (stderr) {
console.error(chalk.red('Error output:'), stderr);
}
// Add a small delay between prompts to avoid overwhelming
if (i < selectedPrompts.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
catch (error) {
console.error(chalk.red(`Failed to execute prompt ${i + 1}:`), error.message);
const { continueExecution } = await inquirer.prompt([
{
type: 'confirm',
name: 'continueExecution',
message: 'Continue with remaining prompts?',
default: true
}
]);
if (!continueExecution) {
process.exit(1);
}
}
}
console.log(chalk.green('\nā
All prompts executed successfully!'));
}
catch (error) {
console.error(chalk.red('Error:'), error.message);
process.exit(1);
}
});
program
.command('list')
.description('List your recent shares')
.action(async () => {
console.log(chalk.yellow('This feature is coming soon!'));
});
program.parse();
//# sourceMappingURL=cli.js.map