@agentuity/cli
Advanced tools
| import type { Logger } from '@/logger'; | ||
| import type { TemplateInfo } from './templates'; | ||
| interface DownloadOptions { | ||
| dest: string; | ||
| template: TemplateInfo; | ||
| templateDir?: string; | ||
| templateBranch?: string; | ||
| } | ||
| interface SetupOptions { | ||
| dest: string; | ||
| projectName: string; | ||
| dirName: string; | ||
| noInstall: boolean; | ||
| noBuild: boolean; | ||
| logger: Logger; | ||
| } | ||
| export declare function downloadTemplate(options: DownloadOptions): Promise<void>; | ||
| export declare function setupProject(options: SetupOptions): Promise<void>; | ||
| export {}; | ||
| //# sourceMappingURL=download.d.ts.map |
| {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/download.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,eAAe;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,YAAY,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,YAAY;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E9E;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CvE"} |
| import type { Logger } from '@/logger'; | ||
| interface CreateFlowOptions { | ||
| projectName?: string; | ||
| template?: string; | ||
| templateDir?: string; | ||
| templateBranch?: string; | ||
| noInstall: boolean; | ||
| noBuild: boolean; | ||
| skipPrompts: boolean; | ||
| logger: Logger; | ||
| } | ||
| export declare function runCreateFlow(options: CreateFlowOptions): Promise<void>; | ||
| export {}; | ||
| //# sourceMappingURL=template-flow.d.ts.map |
| {"version":3,"file":"template-flow.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/template-flow.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAKvC,UAAU,iBAAiB;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA+I7E"} |
| export interface TemplateInfo { | ||
| id: string; | ||
| name: string; | ||
| description: string; | ||
| directory: string; | ||
| } | ||
| export declare function fetchTemplates(localDir?: string, branch?: string): Promise<TemplateInfo[]>; | ||
| //# sourceMappingURL=templates.d.ts.map |
| {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/templates.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAsB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgChG"} |
| export interface DownloadOptions { | ||
| url: string; | ||
| headers?: Record<string, string>; | ||
| message?: string; | ||
| onProgress?: (percent: number, downloadedBytes: number, totalBytes: number) => void; | ||
| } | ||
| /** | ||
| * Download a file with progress tracking | ||
| * Returns the response body stream for further processing | ||
| */ | ||
| export declare function downloadWithProgress(options: DownloadOptions): Promise<NodeJS.ReadableStream>; | ||
| /** | ||
| * Download a file with a TUI spinner showing progress | ||
| */ | ||
| export declare function downloadWithSpinner<T>(options: DownloadOptions, processor: (stream: NodeJS.ReadableStream) => Promise<T>): Promise<T>; | ||
| /** | ||
| * Download a GitHub tarball with progress tracking | ||
| */ | ||
| export interface DownloadGitHubOptions { | ||
| repo: string; | ||
| branch?: string; | ||
| message?: string; | ||
| } | ||
| export declare function downloadGitHubTarball(options: DownloadGitHubOptions): Promise<NodeJS.ReadableStream>; | ||
| //# sourceMappingURL=download.d.ts.map |
| {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../src/download.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACpF;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACzC,OAAO,EAAE,eAAe,GACtB,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAsChC;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAC1C,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GACtD,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,qBAAqB,GAC5B,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAKhC"} |
| import { join } from 'node:path'; | ||
| import { existsSync, mkdirSync, renameSync, readdirSync, cpSync, rmSync } from 'node:fs'; | ||
| import { pipeline } from 'node:stream/promises'; | ||
| import { createGunzip } from 'node:zlib'; | ||
| import { extract } from 'tar-fs'; | ||
| import type { Logger } from '@/logger'; | ||
| import * as tui from '@/tui'; | ||
| import { downloadWithSpinner } from '@/download'; | ||
| import type { TemplateInfo } from './templates'; | ||
| const GITHUB_REPO = 'agentuity/sdk'; | ||
| const GITHUB_BRANCH = 'main'; | ||
| interface DownloadOptions { | ||
| dest: string; | ||
| template: TemplateInfo; | ||
| templateDir?: string; | ||
| templateBranch?: string; | ||
| } | ||
| interface SetupOptions { | ||
| dest: string; | ||
| projectName: string; | ||
| dirName: string; | ||
| noInstall: boolean; | ||
| noBuild: boolean; | ||
| logger: Logger; | ||
| } | ||
| export async function downloadTemplate(options: DownloadOptions): Promise<void> { | ||
| const { dest, template, templateDir, templateBranch } = options; | ||
| mkdirSync(dest, { recursive: true }); | ||
| // Copy from local directory if provided | ||
| if (templateDir) { | ||
| const { resolve } = await import('node:path'); | ||
| const sourceDir = resolve(join(templateDir, template.directory)); | ||
| if (!existsSync(sourceDir)) { | ||
| throw new Error(`Template directory not found: ${sourceDir}`); | ||
| } | ||
| tui.info(`📦 Copying template from ${sourceDir}...`); | ||
| // Copy all files from source to dest | ||
| const files = readdirSync(sourceDir); | ||
| for (const file of files) { | ||
| cpSync(join(sourceDir, file), join(dest, file), { recursive: true }); | ||
| } | ||
| // Rename gitignore -> .gitignore | ||
| const gi = join(dest, 'gitignore'); | ||
| if (existsSync(gi)) { | ||
| renameSync(gi, join(dest, '.gitignore')); | ||
| } | ||
| return; | ||
| } | ||
| // Download from GitHub | ||
| const branch = templateBranch || GITHUB_BRANCH; | ||
| const templatePath = `templates/${template.directory}`; | ||
| const url = `https://codeload.github.com/${GITHUB_REPO}/tar.gz/${branch}`; | ||
| const tempDir = join(dest, '.temp-download'); | ||
| mkdirSync(tempDir, { recursive: true }); | ||
| await downloadWithSpinner( | ||
| { | ||
| url, | ||
| message: templateBranch | ||
| ? `Downloading template files from branch ${branch}...` | ||
| : 'Downloading template files...', | ||
| }, | ||
| async (stream) => { | ||
| // Extract only the template directory from tarball | ||
| await pipeline( | ||
| stream, | ||
| createGunzip(), | ||
| extract(tempDir, { | ||
| map: (header) => { | ||
| const prefix = `sdk-${branch}/${templatePath}/`; | ||
| if (header.name.startsWith(prefix)) { | ||
| header.name = header.name.substring(prefix.length); | ||
| return header; | ||
| } | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| return null as any; | ||
| }, | ||
| }) | ||
| ); | ||
| } | ||
| ); | ||
| // Move files from temp to dest | ||
| const files = readdirSync(tempDir); | ||
| for (const file of files) { | ||
| cpSync(join(tempDir, file), join(dest, file), { recursive: true }); | ||
| } | ||
| rmSync(tempDir, { recursive: true, force: true }); | ||
| // Rename gitignore -> .gitignore | ||
| const gi = join(dest, 'gitignore'); | ||
| if (existsSync(gi)) { | ||
| renameSync(gi, join(dest, '.gitignore')); | ||
| } | ||
| } | ||
| export async function setupProject(options: SetupOptions): Promise<void> { | ||
| const { dest, projectName, dirName, noInstall, noBuild, logger } = options; | ||
| process.chdir(dest); | ||
| // Replace {{PROJECT_NAME}} in files | ||
| tui.info(`🔧 Setting up ${projectName}...`); | ||
| await replaceInFiles(dest, projectName, dirName); | ||
| // Run setup.ts if it exists (legacy) | ||
| if (await Bun.file('./setup.ts').exists()) { | ||
| await tui.spinner({ | ||
| message: 'Running setup script...', | ||
| callback: async () => { | ||
| const proc = Bun.spawn(['bun', './setup.ts'], { stdio: ['pipe', 'pipe', 'pipe'] }); | ||
| const exitCode = await proc.exited; | ||
| if (exitCode !== 0) { | ||
| logger.error('Setup script failed'); | ||
| } | ||
| }, | ||
| }); | ||
| } | ||
| // Install dependencies | ||
| if (!noInstall) { | ||
| const exitCode = await tui.runCommand({ | ||
| command: 'bun install', | ||
| cmd: ['bun', 'install'], | ||
| clearOnSuccess: true, | ||
| }); | ||
| if (exitCode !== 0) { | ||
| logger.error('Failed to install dependencies'); | ||
| } | ||
| } | ||
| // Build project | ||
| if (!noBuild) { | ||
| const exitCode = await tui.runCommand({ | ||
| command: 'bun run build', | ||
| cmd: ['bun', 'run', 'build'], | ||
| clearOnSuccess: true, | ||
| }); | ||
| if (exitCode !== 0) { | ||
| logger.error('Failed to build project'); | ||
| } | ||
| } | ||
| } | ||
| async function replaceInFiles(dir: string, projectName: string, dirName: string): Promise<void> { | ||
| const filesToReplace = ['package.json', 'README.md', 'AGENTS.md']; | ||
| for (const file of filesToReplace) { | ||
| const filePath = join(dir, file); | ||
| const bunFile = Bun.file(filePath); | ||
| if (await bunFile.exists()) { | ||
| let content = await bunFile.text(); | ||
| // Replace human-readable name in most places | ||
| content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName); | ||
| // Replace with directory name for package.json "name" field (npm package name) | ||
| if (file === 'package.json') { | ||
| content = content.replace(/"name":\s*".*?"/, `"name": "${dirName}"`); | ||
| } | ||
| await Bun.write(filePath, content); | ||
| } | ||
| } | ||
| } |
| import { basename, resolve } from 'node:path'; | ||
| import { existsSync, readdirSync, rmSync } from 'node:fs'; | ||
| import enquirer from 'enquirer'; | ||
| import type { Logger } from '@/logger'; | ||
| import * as tui from '@/tui'; | ||
| import { fetchTemplates, type TemplateInfo } from './templates'; | ||
| import { downloadTemplate, setupProject } from './download'; | ||
| interface CreateFlowOptions { | ||
| projectName?: string; | ||
| template?: string; | ||
| templateDir?: string; | ||
| templateBranch?: string; | ||
| noInstall: boolean; | ||
| noBuild: boolean; | ||
| skipPrompts: boolean; | ||
| logger: Logger; | ||
| } | ||
| export async function runCreateFlow(options: CreateFlowOptions): Promise<void> { | ||
| const { | ||
| projectName: initialProjectName, | ||
| template: initialTemplate, | ||
| templateDir, | ||
| templateBranch, | ||
| skipPrompts, | ||
| logger, | ||
| } = options; | ||
| // Step 1: Fetch available templates | ||
| if (templateDir) { | ||
| tui.info(`📋 Loading templates from local directory: ${templateDir}...\n`); | ||
| } else if (templateBranch) { | ||
| tui.info(`📋 Fetching available templates from branch: ${templateBranch}...\n`); | ||
| } else { | ||
| tui.info('📋 Fetching available templates...\n'); | ||
| } | ||
| const templates = await fetchTemplates(templateDir, templateBranch); | ||
| if (templates.length === 0) { | ||
| logger.fatal('No templates available'); | ||
| } | ||
| // Step 2: Get project name | ||
| let projectName = initialProjectName; | ||
| if (!projectName && !skipPrompts) { | ||
| const response = await enquirer.prompt<{ name: string }>({ | ||
| type: 'input', | ||
| name: 'name', | ||
| message: 'What is the name of your project?', | ||
| initial: 'My First Agent', | ||
| validate: (value: string) => { | ||
| if (!value || value.trim().length === 0) { | ||
| return 'Project name is required'; | ||
| } | ||
| return true; | ||
| }, | ||
| }); | ||
| projectName = response.name; | ||
| } | ||
| projectName = projectName || 'My First Agent'; | ||
| // Step 3: Generate disk-friendly directory name | ||
| const dirName = projectName === '.' ? '.' : sanitizeDirectoryName(projectName); | ||
| // Step 4: Determine destination directory | ||
| const dest = dirName === '.' ? process.cwd() : resolve(process.cwd(), dirName); | ||
| const destExists = existsSync(dest); | ||
| const destEmpty = destExists ? readdirSync(dest).length === 0 : true; | ||
| if (destExists && !destEmpty && dirName !== '.') { | ||
| // In TTY mode, ask if they want to overwrite | ||
| if (process.stdin.isTTY && !skipPrompts) { | ||
| tui.warning(`Directory ${dirName} already exists and is not empty.\n`); | ||
| const response = await enquirer.prompt<{ overwrite: boolean }>({ | ||
| type: 'confirm', | ||
| name: 'overwrite', | ||
| message: 'Delete and overwrite the directory?', | ||
| initial: false, | ||
| }); | ||
| if (!response.overwrite) { | ||
| tui.info('Operation cancelled'); | ||
| process.exit(0); | ||
| } | ||
| // Delete the existing directory | ||
| rmSync(dest, { recursive: true, force: true }); | ||
| tui.success(`Deleted ${dirName}\n`); | ||
| } else { | ||
| logger.fatal(`Directory ${dirName} already exists and is not empty.`); | ||
| } | ||
| } | ||
| // Show directory and name confirmation | ||
| if (!skipPrompts) { | ||
| const displayPath = dirName === '.' ? basename(dest) : dirName; | ||
| tui.info(`📁 Project: ${tui.bold(projectName)}`); | ||
| tui.info(`📂 Directory: ${tui.bold(displayPath)}\n`); | ||
| } | ||
| // Step 5: Select template | ||
| let selectedTemplate: TemplateInfo; | ||
| if (initialTemplate) { | ||
| const found = templates.find((t) => t.id === initialTemplate); | ||
| if (!found) { | ||
| logger.fatal(`Template "${initialTemplate}" not found`); | ||
| return; | ||
| } | ||
| selectedTemplate = found; | ||
| } else if (skipPrompts) { | ||
| selectedTemplate = templates[0]; | ||
| } else { | ||
| const response = await enquirer.prompt<{ template: string }>({ | ||
| type: 'select', | ||
| name: 'template', | ||
| message: 'Select a template:', | ||
| choices: templates.map((t) => ({ | ||
| name: t.id, | ||
| message: `${t.name.padEnd(15, ' ')} ${tui.muted(t.description)}`, | ||
| })), | ||
| }); | ||
| const found = templates.find((t) => t.id === response.template); | ||
| if (!found) { | ||
| logger.fatal('Template selection failed'); | ||
| return; | ||
| } | ||
| selectedTemplate = found; | ||
| } | ||
| tui.info(`✨ Using template: ${tui.bold(selectedTemplate.name)}`); | ||
| // Step 6: Download template | ||
| await downloadTemplate({ | ||
| dest, | ||
| template: selectedTemplate, | ||
| templateDir, | ||
| templateBranch, | ||
| }); | ||
| // Step 7: Setup project (replace placeholders, install deps, build) | ||
| await setupProject({ | ||
| dest, | ||
| projectName: projectName === '.' ? basename(dest) : projectName, | ||
| dirName: dirName === '.' ? basename(dest) : dirName, | ||
| noInstall: options.noInstall, | ||
| noBuild: options.noBuild, | ||
| logger, | ||
| }); | ||
| // Step 8: Show completion message | ||
| tui.success('✨ Project created successfully!\n'); | ||
| tui.info('Next steps:'); | ||
| if (dirName !== '.') { | ||
| tui.newline(); | ||
| console.log(` 1. ${tui.bold(`cd ${dirName}`)}`); | ||
| console.log(` 2. ${tui.bold('bun run dev')}`); | ||
| } else { | ||
| console.log(` 1. ${tui.bold('bun run dev')}`); | ||
| } | ||
| tui.newline(); | ||
| console.log(`Your agents will be running at ${tui.link('http://localhost:3000')}`); | ||
| } | ||
| /** | ||
| * Sanitize a project name to create a safe directory/package name | ||
| * - Converts to lowercase | ||
| * - Replaces spaces and underscores with hyphens | ||
| * - Removes unsafe characters | ||
| * - Ensures it starts with a letter or number | ||
| */ | ||
| function sanitizeDirectoryName(name: string): string { | ||
| return name | ||
| .toLowerCase() | ||
| .trim() | ||
| .replace(/\s+/g, '-') // Replace spaces with hyphens | ||
| .replace(/_+/g, '-') // Replace underscores with hyphens | ||
| .replace(/[^a-z0-9-]/g, '') // Remove non-alphanumeric except hyphens | ||
| .replace(/-+/g, '-') // Collapse multiple hyphens | ||
| .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens | ||
| .replace(/^[^a-z0-9]+/, ''); // Remove leading non-alphanumeric | ||
| } |
| const GITHUB_REPO = 'agentuity/sdk'; | ||
| const GITHUB_BRANCH = 'main'; | ||
| const TEMPLATES_JSON_PATH = 'templates/templates.json'; | ||
| export interface TemplateInfo { | ||
| id: string; | ||
| name: string; | ||
| description: string; | ||
| directory: string; | ||
| } | ||
| interface TemplatesManifest { | ||
| templates: TemplateInfo[]; | ||
| } | ||
| export async function fetchTemplates(localDir?: string, branch?: string): Promise<TemplateInfo[]> { | ||
| // Load from local directory if provided | ||
| if (localDir) { | ||
| const { join } = await import('node:path'); | ||
| const { resolve } = await import('node:path'); | ||
| const manifestPath = resolve(join(localDir, 'templates.json')); | ||
| const file = Bun.file(manifestPath); | ||
| if (!(await file.exists())) { | ||
| throw new Error(`templates.json not found at ${manifestPath}`); | ||
| } | ||
| const manifest = (await file.json()) as TemplatesManifest; | ||
| return manifest.templates; | ||
| } | ||
| // Fetch from GitHub | ||
| const branchToUse = branch || GITHUB_BRANCH; | ||
| const url = `https://raw.githubusercontent.com/${GITHUB_REPO}/${branchToUse}/${TEMPLATES_JSON_PATH}`; | ||
| const headers: Record<string, string> = {}; | ||
| if (process.env.GITHUB_TOKEN) { | ||
| headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; | ||
| } | ||
| const response = await fetch(url, { headers }); | ||
| if (!response.ok) { | ||
| throw new Error(`Failed to fetch templates: ${response.statusText}`); | ||
| } | ||
| const manifest = (await response.json()) as TemplatesManifest; | ||
| return manifest.templates; | ||
| } |
+101
| import { Transform } from 'node:stream'; | ||
| import * as tui from './tui'; | ||
| export interface DownloadOptions { | ||
| url: string; | ||
| headers?: Record<string, string>; | ||
| message?: string; | ||
| onProgress?: (percent: number, downloadedBytes: number, totalBytes: number) => void; | ||
| } | ||
| /** | ||
| * Download a file with progress tracking | ||
| * Returns the response body stream for further processing | ||
| */ | ||
| export async function downloadWithProgress( | ||
| options: DownloadOptions | ||
| ): Promise<NodeJS.ReadableStream> { | ||
| const { url, headers = {}, onProgress } = options; | ||
| // Add GITHUB_TOKEN if available and not already set | ||
| const requestHeaders = { ...headers }; | ||
| if (process.env.GITHUB_TOKEN && !requestHeaders['Authorization']) { | ||
| requestHeaders['Authorization'] = `Bearer ${process.env.GITHUB_TOKEN}`; | ||
| } | ||
| const response = await fetch(url, { headers: requestHeaders }); | ||
| if (!response.ok) { | ||
| throw new Error(`Download failed: ${response.statusText}`); | ||
| } | ||
| const contentLength = parseInt(response.headers.get('content-length') || '0', 10); | ||
| let downloadedBytes = 0; | ||
| // Create a transform stream that tracks progress | ||
| const progressStream = new Transform({ | ||
| transform(chunk, _encoding, callback) { | ||
| downloadedBytes += chunk.length; | ||
| if (contentLength > 0) { | ||
| const percent = Math.min(100, Math.floor((downloadedBytes / contentLength) * 100)); | ||
| if (onProgress) { | ||
| onProgress(percent, downloadedBytes, contentLength); | ||
| } | ||
| } | ||
| callback(null, chunk); | ||
| }, | ||
| }); | ||
| // Pipe the response through the progress tracker | ||
| const responseStream = response.body as unknown as NodeJS.ReadableStream; | ||
| responseStream.pipe(progressStream); | ||
| return progressStream; | ||
| } | ||
| /** | ||
| * Download a file with a TUI spinner showing progress | ||
| */ | ||
| export async function downloadWithSpinner<T>( | ||
| options: DownloadOptions, | ||
| processor: (stream: NodeJS.ReadableStream) => Promise<T> | ||
| ): Promise<T> { | ||
| const { message = 'Downloading...' } = options; | ||
| return await tui.spinner({ | ||
| type: 'progress', | ||
| message, | ||
| callback: async (updateProgress) => { | ||
| const stream = await downloadWithProgress({ | ||
| ...options, | ||
| onProgress: (percent) => updateProgress(percent), | ||
| }); | ||
| const result = await processor(stream); | ||
| // Ensure we show 100% at the end | ||
| updateProgress(100); | ||
| return result; | ||
| }, | ||
| }); | ||
| } | ||
| /** | ||
| * Download a GitHub tarball with progress tracking | ||
| */ | ||
| export interface DownloadGitHubOptions { | ||
| repo: string; | ||
| branch?: string; | ||
| message?: string; | ||
| } | ||
| export async function downloadGitHubTarball( | ||
| options: DownloadGitHubOptions | ||
| ): Promise<NodeJS.ReadableStream> { | ||
| const { repo, branch = 'main', message = 'Downloading from GitHub...' } = options; | ||
| const url = `https://codeload.github.com/${repo}/tar.gz/${branch}`; | ||
| return await downloadWithSpinner({ url, message }, async (stream) => stream); | ||
| } |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,uBAAuB,wCAgPlC,CAAC"} | ||
| {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/cmd/project/create.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,uBAAuB,wCA8ClC,CAAC"} |
+1
-0
@@ -14,2 +14,3 @@ export { createCLI, registerCommands } from './cli'; | ||
| export { playSound } from './sound'; | ||
| export { downloadWithProgress, downloadWithSpinner, downloadGitHubTarball, type DownloadOptions as DownloadOptionsType, type DownloadGitHubOptions, } from './download'; | ||
| export type { Config, LogLevel, GlobalOptions, CommandContext, SubcommandDefinition, CommandDefinition, Profile, AuthData, CommandSchemas, } from './types'; | ||
@@ -16,0 +17,0 @@ export { createSubcommand, createCommand } from './types'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACN,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,OAAO,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,YAAY,EACX,MAAM,EACN,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,cAAc,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChF,OAAO,EACN,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,OAAO,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AACtF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,KAAK,GAAG,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EACN,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,eAAe,IAAI,mBAAmB,EAC3C,KAAK,qBAAqB,GAC1B,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,MAAM,EACN,QAAQ,EACR,aAAa,EACb,cAAc,EACd,oBAAoB,EACpB,iBAAiB,EACjB,OAAO,EACP,QAAQ,EACR,cAAc,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1D,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC"} |
+5
-0
@@ -144,2 +144,7 @@ /** | ||
| env?: Record<string, string>; | ||
| /** | ||
| * If true, clear output on success and only show command + success icon | ||
| * Defaults to false | ||
| */ | ||
| clearOnSuccess?: boolean; | ||
| } | ||
@@ -146,0 +151,0 @@ /** |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../src/tui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA+C9C,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAExD;AAUD;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI7C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI3C;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI7C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIzC;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUxC;AAuBD;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAE9B;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAKvE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CAKtE;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA6ExD;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,SAA+B,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCzF;AAED;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDpF;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8CpE;AA8DD;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACtC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACxC,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5D;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,oBAAoB,CAAC,CAAC,CAAC,GAAG,sBAAsB,CAAC,CAAC,CAAC,CAAC;AAEpF;;;;;;;;GAQG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEd;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AA8FzE;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACpC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,GAAG,EAAE,MAAM,EAAE,CAAC;IACd;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuL/E"} |
+6
-6
| { | ||
| "name": "@agentuity/cli", | ||
| "version": "0.0.10", | ||
| "version": "0.0.11", | ||
| "type": "module", | ||
@@ -27,9 +27,7 @@ "main": "./src/index.ts", | ||
| "typecheck": "bunx tsc --noEmit", | ||
| "test": "bun scripts/test-create-integration.ts", | ||
| "prepublishOnly": "bun run clean && bun run build", | ||
| "setup-dev-template": "bun scripts/setup-dev-template.ts", | ||
| "simulate-create": "bun scripts/simulate-bun-create.ts" | ||
| "test": "bun scripts/test-create-flow.ts", | ||
| "prepublishOnly": "bun run clean && bun run build" | ||
| }, | ||
| "dependencies": { | ||
| "@agentuity/core": "0.0.9", | ||
| "@agentuity/core": "0.0.11", | ||
| "acorn-loose": "^8.5.2", | ||
@@ -39,5 +37,7 @@ "astring": "^1.9.0", | ||
| "enquirer": "^2.4.1", | ||
| "tar-fs": "^3.1.1", | ||
| "zod": "^4.1.12" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/tar-fs": "^2.0.4", | ||
| "typescript": "^5.9.0" | ||
@@ -44,0 +44,0 @@ }, |
+24
-220
| import { createSubcommand } from '@/types'; | ||
| import { z } from 'zod'; | ||
| import enquirer from 'enquirer'; | ||
| import { existsSync, readdirSync } from 'node:fs'; | ||
| import { resolve, basename, join } from 'node:path'; | ||
| import { runCreateFlow } from './template-flow'; | ||
@@ -17,2 +15,11 @@ export const createProjectSubcommand = createSubcommand({ | ||
| dir: z.string().optional().describe('Directory to create the project in'), | ||
| template: z.string().optional().describe('Template to use'), | ||
| templateDir: z | ||
| .string() | ||
| .optional() | ||
| .describe('Local template directory for testing (e.g., ./packages/templates)'), | ||
| templateBranch: z | ||
| .string() | ||
| .optional() | ||
| .describe('GitHub branch to use for templates (default: main)'), | ||
| install: z | ||
@@ -23,8 +30,8 @@ .boolean() | ||
| .describe('Run bun install after creating the project (use --no-install to skip)'), | ||
| confirm: z.boolean().optional().describe('Skip confirmation prompts'), | ||
| fromBunCreate: z | ||
| build: z | ||
| .boolean() | ||
| .optional() | ||
| .describe('Internal: called from bun create postinstall'), | ||
| dev: z.boolean().optional().describe('Internal: use local template for testing'), | ||
| .default(true) | ||
| .describe('Run bun run build after installing (use --no-build to skip)'), | ||
| confirm: z.boolean().optional().describe('Skip confirmation prompts'), | ||
| }), | ||
@@ -35,216 +42,13 @@ }, | ||
| const { logger, opts } = ctx; | ||
| // Case 2: Called from bun create postinstall | ||
| if (opts.fromBunCreate) { | ||
| const projectDir = process.cwd(); | ||
| const packageJsonPath = join(projectDir, 'package.json'); | ||
| if (!existsSync(packageJsonPath)) { | ||
| logger.error('package.json not found in current directory'); | ||
| return; | ||
| } | ||
| // Disable log prefixes for cleaner postinstall output | ||
| logger.setShowPrefix(false); | ||
| const packageJsonFile = Bun.file(packageJsonPath); | ||
| const packageJson = await packageJsonFile.json(); | ||
| const projectName = packageJson.name || basename(projectDir); | ||
| logger.info(`\n🔧 Setting up ${projectName}...\n`); | ||
| // Update package.json - remove bun-create metadata | ||
| packageJson.name = projectName; | ||
| delete packageJson['bun-create']; | ||
| delete packageJson.bin; | ||
| packageJson.private = true; | ||
| delete packageJson.files; | ||
| delete packageJson.keywords; | ||
| delete packageJson.author; | ||
| delete packageJson.license; | ||
| delete packageJson.publishConfig; | ||
| delete packageJson.description; | ||
| // Remove enquirer from dependencies (only needed for setup) | ||
| if (packageJson.dependencies) { | ||
| delete packageJson.dependencies.enquirer; | ||
| } | ||
| await Bun.write(packageJsonPath, JSON.stringify(packageJson, null, '\t')); | ||
| logger.info('✓ Updated package.json'); | ||
| // Update README.md | ||
| const readmePath = join(projectDir, 'README.md'); | ||
| if (existsSync(readmePath)) { | ||
| const readmeFile = Bun.file(readmePath); | ||
| let readme = await readmeFile.text(); | ||
| readme = readme.replace(/\{\{PROJECT_NAME\}\}/g, projectName); | ||
| await Bun.write(readmePath, readme); | ||
| logger.info('✓ Updated README.md'); | ||
| } | ||
| // Update AGENTS.md | ||
| const agentsMdPath = join(projectDir, 'AGENTS.md'); | ||
| if (existsSync(agentsMdPath)) { | ||
| const agentsMdFile = Bun.file(agentsMdPath); | ||
| let agentsMd = await agentsMdFile.text(); | ||
| agentsMd = agentsMd.replace(/\{\{PROJECT_NAME\}\}/g, projectName); | ||
| await Bun.write(agentsMdPath, agentsMd); | ||
| logger.info('✓ Updated AGENTS.md'); | ||
| } | ||
| // Remove setup files | ||
| const filesToRemove = ['setup.ts']; | ||
| for (const file of filesToRemove) { | ||
| const filePath = join(projectDir, file); | ||
| if (existsSync(filePath)) { | ||
| await Bun.$`rm ${filePath}`; | ||
| logger.info('✓ Removed ${file}'); | ||
| } | ||
| } | ||
| logger.info('\n✨ Setup complete!\n'); | ||
| return; | ||
| } | ||
| // Case 1: Normal CLI flow | ||
| // Relaxed validation: any reasonable name between 2-64 characters | ||
| const isValidProjectName = (name: string): boolean => { | ||
| return name.trim().length >= 2 && name.trim().length <= 64; | ||
| }; | ||
| // Transform name to URL and disk-friendly format | ||
| const transformToDirectoryName = (name: string): string => { | ||
| const result = name | ||
| .trim() | ||
| .toLowerCase() | ||
| .replace(/[^a-z0-9]+/g, '-') // Replace non-alphanumeric with hyphens | ||
| .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens | ||
| .replace(/-+/g, '-') // Replace consecutive hyphens with single hyphen | ||
| .substring(0, 64); // Ensure max length | ||
| // Validate result is non-empty (happens when name contains only special chars) | ||
| if (!result) { | ||
| throw new Error( | ||
| `Invalid project name "${name}": must contain at least one alphanumeric character` | ||
| ); | ||
| } | ||
| return result; | ||
| }; | ||
| // Get project name | ||
| let projectName = opts.name; | ||
| while (!projectName || !isValidProjectName(projectName)) { | ||
| const result = await enquirer.prompt<{ name: string }>({ | ||
| type: 'input', | ||
| name: 'name', | ||
| message: 'Project name:', | ||
| initial: projectName, | ||
| validate: (value: string) => { | ||
| if (!value) return 'Project name is required'; | ||
| if (!isValidProjectName(value)) { | ||
| return 'Project name must be between 2 and 64 characters'; | ||
| } | ||
| return true; | ||
| }, | ||
| }); | ||
| projectName = result.name; | ||
| } | ||
| projectName = projectName.trim(); | ||
| const projectDirName = transformToDirectoryName(projectName); | ||
| // Get directory - if specified, create the project there, otherwise create in current dir | ||
| const baseDir = opts.dir ? resolve(opts.dir) : process.cwd(); | ||
| const targetDir = resolve(baseDir, projectDirName); | ||
| // Check if directory exists and validate | ||
| let shouldProceed = true; | ||
| if (existsSync(targetDir)) { | ||
| const files = readdirSync(targetDir); | ||
| const hasFiles = files.length > 0; | ||
| if (hasFiles) { | ||
| if (opts.confirm === false) { | ||
| logger.error(`Directory ${targetDir} is not empty and --no-confirm was specified`); | ||
| return; | ||
| } | ||
| // Require explicit confirmation in non-TTY environments | ||
| if (opts.confirm !== true && !process.stdin.isTTY) { | ||
| logger.error( | ||
| `Directory "${targetDir}" is not empty. Use --confirm flag in non-interactive environments.` | ||
| ); | ||
| return; | ||
| } | ||
| // Interactive prompt in TTY environments | ||
| if (opts.confirm !== true && process.stdin.isTTY) { | ||
| const result = await enquirer.prompt<{ proceed: boolean }>({ | ||
| type: 'confirm', | ||
| name: 'proceed', | ||
| message: `Directory "${targetDir}" is not empty. Files may be overwritten. Continue?`, | ||
| initial: false, | ||
| }); | ||
| shouldProceed = result.proceed; | ||
| } | ||
| if (!shouldProceed) { | ||
| logger.info('Operation cancelled'); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| // Print collected values | ||
| logger.info('\n=== Project Configuration ==='); | ||
| logger.info(`Name: ${projectName}`); | ||
| logger.info(`Directory Name: ${projectDirName}`); | ||
| logger.info(`Target Directory: ${targetDir}`); | ||
| logger.info('=============================\n'); | ||
| // Run bun create to scaffold the project | ||
| logger.info('Creating project from template...'); | ||
| try { | ||
| // Determine template name based on dev mode | ||
| const templateName = opts.dev ? 'agentuity-dev' : 'agentuity'; | ||
| if (opts.dev) { | ||
| logger.info('🔧 Dev mode: Using local template'); | ||
| } | ||
| // Build bun create command args | ||
| // Note: bun create supports --no-install to skip dependency installation | ||
| const bunCreateArgs = ['bun', 'create']; | ||
| if (opts.install === false) { | ||
| bunCreateArgs.push('--no-install'); | ||
| } | ||
| bunCreateArgs.push(templateName, projectDirName); | ||
| logger.info(`Running: ${bunCreateArgs.join(' ')}`); | ||
| const result = Bun.spawn(bunCreateArgs, { | ||
| cwd: baseDir, | ||
| stdout: 'inherit', | ||
| stderr: 'inherit', | ||
| stdin: 'inherit', | ||
| }); | ||
| const exitCode = await result.exited; | ||
| if (exitCode !== 0) { | ||
| throw new Error(`bun create exited with code ${exitCode}`); | ||
| } | ||
| logger.info('\n✨ Project created successfully!'); | ||
| logger.info(`\nNext steps:`); | ||
| logger.info(` cd ${projectDirName}`); | ||
| logger.info(` bun run dev`); | ||
| } catch (error) { | ||
| logger.error('Failed to create project:', error); | ||
| throw error; | ||
| } | ||
| await runCreateFlow({ | ||
| projectName: opts.name, | ||
| template: opts.template, | ||
| templateDir: opts.templateDir, | ||
| templateBranch: opts.templateBranch, | ||
| noInstall: opts.install === false, | ||
| noBuild: opts.build === false, | ||
| skipPrompts: opts.confirm === true, | ||
| logger, | ||
| }); | ||
| }, | ||
| }); |
+7
-0
@@ -27,2 +27,9 @@ export { createCLI, registerCommands } from './cli'; | ||
| export { playSound } from './sound'; | ||
| export { | ||
| downloadWithProgress, | ||
| downloadWithSpinner, | ||
| downloadGitHubTarball, | ||
| type DownloadOptions as DownloadOptionsType, | ||
| type DownloadGitHubOptions, | ||
| } from './download'; | ||
| export type { | ||
@@ -29,0 +36,0 @@ Config, |
+33
-15
@@ -40,3 +40,3 @@ /** | ||
| light: Bun.color('#808080', 'ansi') || '\x1b[90m', // gray | ||
| dark: Bun.color('#DDDDDD', 'ansi') || '\x1b[37m', // light gray | ||
| dark: Bun.color('#888888', 'ansi') || '\x1b[90m', // darker gray | ||
| }, | ||
@@ -657,2 +657,7 @@ bold: { | ||
| env?: Record<string, string>; | ||
| /** | ||
| * If true, clear output on success and only show command + success icon | ||
| * Defaults to false | ||
| */ | ||
| clearOnSuccess?: boolean; | ||
| } | ||
@@ -671,3 +676,3 @@ | ||
| export async function runCommand(options: CommandRunnerOptions): Promise<number> { | ||
| const { command, cmd, cwd, env } = options; | ||
| const { command, cmd, cwd, env, clearOnSuccess = false } = options; | ||
| const isTTY = process.stdout.isTTY; | ||
@@ -791,5 +796,2 @@ | ||
| // Determine how many lines to show in final output | ||
| const finalLinesToShow = exitCode === 0 ? 3 : 10; | ||
| // Move cursor up to redraw final state | ||
@@ -800,14 +802,30 @@ if (linesRendered > 0) { | ||
| // Show final status with appropriate color | ||
| const statusColor = exitCode === 0 ? green : red; | ||
| process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`); | ||
| // Clear all lines if clearOnSuccess is true and command succeeded | ||
| if (clearOnSuccess && exitCode === 0) { | ||
| // Clear all rendered lines | ||
| for (let i = 0; i < linesRendered; i++) { | ||
| process.stdout.write('\r\x1b[K\n'); | ||
| } | ||
| // Move cursor back up | ||
| process.stdout.write(`\x1b[${linesRendered}A`); | ||
| // Show final output lines | ||
| const finalOutputLines = allOutputLines.slice(-finalLinesToShow); | ||
| for (const line of finalOutputLines) { | ||
| let displayLine = line; | ||
| if (getDisplayWidth(displayLine) > maxLineWidth) { | ||
| displayLine = displayLine.slice(0, maxLineWidth - 3) + '...'; | ||
| // Show compact success: ✓ command | ||
| process.stdout.write(`\r\x1b[K${green}${ICONS.success}${reset} ${cmdColor}${displayCmd}${reset}\n`); | ||
| } else { | ||
| // Determine how many lines to show in final output | ||
| const finalLinesToShow = exitCode === 0 ? 3 : 10; | ||
| // Show final status with appropriate color | ||
| const statusColor = exitCode === 0 ? green : red; | ||
| process.stdout.write(`\r\x1b[K${statusColor}$${reset} ${cmdColor}${displayCmd}${reset}\n`); | ||
| // Show final output lines | ||
| const finalOutputLines = allOutputLines.slice(-finalLinesToShow); | ||
| for (const line of finalOutputLines) { | ||
| let displayLine = line; | ||
| if (getDisplayWidth(displayLine) > maxLineWidth) { | ||
| displayLine = displayLine.slice(0, maxLineWidth - 3) + '...'; | ||
| } | ||
| process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`); | ||
| } | ||
| process.stdout.write(`\r\x1b[K${mutedColor}${displayLine}${reset}\n`); | ||
| } | ||
@@ -814,0 +832,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 16 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 15 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
221135
6.83%170
7.59%5198
7.64%7
16.67%2
100%37
15.63%4
300%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
Updated