Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@agentuity/cli

Package Overview
Dependencies
Maintainers
2
Versions
251
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agentuity/cli - npm Package Compare versions

Comparing version
0.0.10
to
0.0.11
+20
dist/cmd/project/download.d.ts
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;
}
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

@@ -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"}

@@ -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"}

@@ -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":"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"}
{
"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 @@ },

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,
});
},
});

@@ -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,

@@ -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 @@