@bitcoin-os/bapps
Version:
Create new Bitcoin apps with Next.js and Bitcoin OS integration
410 lines (344 loc) ⢠12.6 kB
JavaScript
#!/usr/bin/env node
const { Command } = require('commander');
const inquirer = require('inquirer');
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const { execSync } = require('child_process');
const program = new Command();
const TEMPLATES = {
basic: 'Basic Bitcoin App',
writer: 'Document Editor (like Bitcoin Writer)',
drive: 'File Storage (like Bitcoin Drive)',
email: 'Email Client (like Bitcoin Email)',
music: 'Media Player (like Bitcoin Music)',
calendar: 'Calendar App (like Bitcoin Calendar)',
exchange: 'Trading Platform (like Bitcoin Exchange)',
wallet: 'Wallet Interface (like Bitcoin Wallet)',
custom: 'Custom App (minimal setup)'
};
const COLORS = {
'text-orange-500': '#f97316',
'text-yellow-500': '#eab308',
'text-red-500': '#ef4444',
'text-purple-500': '#a855f7',
'text-fuchsia-500': '#d946ef',
'text-green-500': '#22c55e',
'text-blue-500': '#3b82f6',
'text-sky-400': '#38bdf8',
'text-cyan-500': '#06b6d4',
'text-pink-500': '#ec4899'
};
program
.name('create-bapp')
.description('Create a new Bitcoin app with Next.js and Bitcoin OS integration')
.version('1.0.0')
.argument('[app-name]', 'Name of the app to create')
.option('-t, --template <template>', 'Template to use')
.option('-y, --yes', 'Skip prompts and use defaults')
.action(async (appName, options) => {
try {
console.log(chalk.bitcoin('āæ'), chalk.bold.magenta('Bitcoin OS App Creator'));
console.log(chalk.gray('Creating a new Bitcoin app with full OS integration\n'));
let answers = {};
if (!appName || !options.yes) {
const questions = [];
if (!appName) {
questions.push({
type: 'input',
name: 'appName',
message: 'What is your app name?',
default: appName || 'my-bitcoin-app',
validate: (input) => {
if (!input.trim()) return 'App name is required';
if (!/^[a-z0-9-]+$/.test(input)) return 'App name must be lowercase with hyphens only';
return true;
}
});
}
if (!options.template) {
questions.push({
type: 'list',
name: 'template',
message: 'Choose a template:',
choices: Object.entries(TEMPLATES).map(([key, value]) => ({
name: value,
value: key
}))
});
}
questions.push(
{
type: 'input',
name: 'displayName',
message: 'Display name for your app:',
default: (answers) => {
const name = answers.appName || appName;
return name.split('-').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ');
}
},
{
type: 'list',
name: 'colorScheme',
message: 'Choose a color scheme:',
choices: Object.entries(COLORS).map(([key, value]) => ({
name: `${key} (${value})`,
value: key
}))
},
{
type: 'input',
name: 'description',
message: 'App description:',
default: 'A Bitcoin app built with Bitcoin OS'
},
{
type: 'confirm',
name: 'includeAuth',
message: 'Include HandCash authentication?',
default: true
},
{
type: 'confirm',
name: 'includeBlockchain',
message: 'Include blockchain integration?',
default: true
},
{
type: 'confirm',
name: 'includePayments',
message: 'Include payment processing?',
default: false
}
);
answers = await inquirer.prompt(questions);
}
const config = {
appName: appName || answers.appName,
template: options.template || answers.template || 'basic',
displayName: answers.displayName || appName || 'My Bitcoin App',
colorScheme: answers.colorScheme || 'text-orange-500',
description: answers.description || 'A Bitcoin app built with Bitcoin OS',
includeAuth: options.yes ? true : answers.includeAuth,
includeBlockchain: options.yes ? true : answers.includeBlockchain,
includePayments: options.yes ? false : answers.includePayments
};
await createApp(config);
} catch (error) {
console.error(chalk.red('Error creating app:'), error.message);
process.exit(1);
}
});
async function createApp(config) {
const { appName, template, displayName, colorScheme, description, includeAuth, includeBlockchain, includePayments } = config;
console.log(chalk.cyan(`\nCreating ${displayName}...`));
const appPath = path.resolve(appName);
if (await fs.pathExists(appPath)) {
throw new Error(`Directory ${appName} already exists`);
}
// Create Next.js app
console.log(chalk.yellow('š¦ Creating Next.js app...'));
execSync(`npx create-next-app@latest ${appName} --typescript --tailwind --eslint --app --src-dir --import-alias "@/*"`, {
stdio: 'inherit'
});
// Install Bitcoin OS dependencies
console.log(chalk.yellow('š§ Installing Bitcoin OS dependencies...'));
process.chdir(appPath);
const dependencies = [
'@bitcoin-os/bridge@latest',
'lucide-react@latest'
];
if (includeAuth) {
dependencies.push('@handcash/handcash-connect@latest');
}
if (includeBlockchain) {
dependencies.push('bsv@latest');
}
if (includePayments) {
dependencies.push('@moneybutton/api-client@latest');
}
execSync(`npm install ${dependencies.join(' ')}`, { stdio: 'inherit' });
// Generate app files
await generateAppFiles(config, appPath);
console.log(chalk.green('\nā
Bitcoin app created successfully!'));
console.log(chalk.cyan('\nNext steps:'));
console.log(chalk.white(` cd ${appName}`));
console.log(chalk.white(' npm run dev'));
console.log(chalk.gray('\nYour app will be available at http://localhost:3000'));
console.log(chalk.gray(`\nApp includes:`));
console.log(chalk.gray(` ⢠Full Bitcoin OS integration (taskbar, dock, sidebar)`));
console.log(chalk.gray(` ⢠${template} template`));
if (includeAuth) console.log(chalk.gray(` ⢠HandCash authentication`));
if (includeBlockchain) console.log(chalk.gray(` ⢠Blockchain integration`));
if (includePayments) console.log(chalk.gray(` ⢠Payment processing`));
}
async function generateAppFiles(config, appPath) {
const { appName, template, displayName, colorScheme, description, includeAuth } = config;
// Generate layout.tsx
const layoutContent = `import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { BitcoinOSProvider } from '@bitcoin-os/bridge'
import './globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: '${displayName}',
description: '${description}',
icons: {
icon: '/favicon.ico',
},
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
const bitcoinOSConfig = {
context: {
appName: '${displayName}',
exchangeUrl: 'https://${appName}-exchange.vercel.app', // Your custom exchange
branding: {
name: '${displayName}',
color: '${colorScheme.includes('text-') ? colorScheme.replace('text-', '') : colorScheme}'
}
},
showDevSidebar: true,
showDock: true,
showPocBar: true,
customStyles: \`
.bitcoin-symbol {
color: ${COLORS[colorScheme] || '#f97316'} !important;
}
\`
}
return (
<html lang="en">
<body className={inter.className}>
<BitcoinOSProvider config={bitcoinOSConfig}>
{children}
</BitcoinOSProvider>
</body>
</html>
)
}`;
await fs.writeFile(path.join(appPath, 'src/app/layout.tsx'), layoutContent);
// Generate main page
const pageContent = `'use client'
import { useState${includeAuth ? ', useEffect' : ''} } from 'react'
${includeAuth ? `import { HandCashConnect } from '@handcash/handcash-connect'` : ''}
export default function Home() {
${includeAuth ? `
const [handcashConnect, setHandcashConnect] = useState<HandCashConnect | null>(null)
const [user, setUser] = useState<any>(null)
useEffect(() => {
const hcc = new HandCashConnect({
appId: process.env.NEXT_PUBLIC_HANDCASH_APP_ID || 'your-app-id',
appSecret: process.env.NEXT_PUBLIC_HANDCASH_APP_SECRET || 'your-app-secret'
})
setHandcashConnect(hcc)
}, [])
const handleAuth = async () => {
if (handcashConnect) {
try {
const authToken = await handcashConnect.requestPermissions()
const account = handcashConnect.getAccountFromAuthToken(authToken)
const profile = await account.profile.getCurrentProfile()
setUser(profile)
} catch (error) {
console.error('Auth error:', error)
}
}
}` : ''}
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm">
<h1 className="text-4xl font-bold text-center mb-8" style={{ color: '${COLORS[colorScheme] || '#f97316'}' }}>
āæ ${displayName}
</h1>
<div className="text-center mb-8">
<p className="text-lg text-gray-600 dark:text-gray-300">
${description}
</p>
</div>
${includeAuth ? `
<div className="text-center">
{user ? (
<div className="bg-green-100 dark:bg-green-900 p-4 rounded-lg">
<p className="text-green-800 dark:text-green-200">
Welcome, {user.displayName || user.handle}!
</p>
</div>
) : (
<button
onClick={handleAuth}
className="bg-orange-500 hover:bg-orange-600 text-white font-bold py-2 px-4 rounded"
>
Connect with HandCash
</button>
)}
</div>` : `
<div className="text-center">
<p className="text-gray-500 dark:text-gray-400">
Start building your Bitcoin app here!
</p>
</div>`}
</div>
</main>
)
}`;
await fs.writeFile(path.join(appPath, 'src/app/page.tsx'), pageContent);
// Update package.json with app info
const packageJsonPath = path.join(appPath, 'package.json');
const packageJson = await fs.readJson(packageJsonPath);
packageJson.name = appName;
packageJson.description = description;
packageJson.keywords = [
...packageJson.keywords || [],
'bitcoin',
'bitcoin-os',
'bapp'
];
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
// Create .env.local template if auth is included
if (includeAuth) {
const envContent = `# HandCash Configuration
NEXT_PUBLIC_HANDCASH_APP_ID=your-app-id
NEXT_PUBLIC_HANDCASH_APP_SECRET=your-app-secret
# Get your credentials from https://dashboard.handcash.io
`;
await fs.writeFile(path.join(appPath, '.env.local.example'), envContent);
}
// Create README
const readmeContent = `# ${displayName}
${description}
Built with [Bitcoin OS](https://bitcoin-os.vercel.app) and Next.js.
## Features
- šØ Full Bitcoin OS integration (taskbar, dock, sidebar)
- ā” Next.js 14 with App Router
- š TypeScript support
- š Tailwind CSS styling
${includeAuth ? '- š HandCash authentication' : ''}
${config.includeBlockchain ? '- āļø Bitcoin SV blockchain integration' : ''}
${config.includePayments ? '- š° Payment processing' : ''}
## Getting Started
\`\`\`bash
npm run dev
\`\`\`
Open [http://localhost:3000](http://localhost:3000) to view your app.
${includeAuth ? `## Setup
1. Copy \`.env.local.example\` to \`.env.local\`
2. Get your HandCash credentials from [dashboard.handcash.io](https://dashboard.handcash.io)
3. Update the environment variables
` : ''}## Learn More
- [Bitcoin OS Documentation](https://bitcoin-os.vercel.app/docs)
- [Next.js Documentation](https://nextjs.org/docs)
- [HandCash Connect](https://docs.handcash.io/docs/handcash-connect)
## Deploy
Deploy easily with [Vercel](https://vercel.com/new) or any Next.js hosting platform.
---
Built with ā¤ļø by [The Bitcoin Corporation](https://thebitcoincorporation.com)
`;
await fs.writeFile(path.join(appPath, 'README.md'), readmeContent);
}
program.parse();