puter-cli
Version:
Command line interface for Puter cloud platform
387 lines (371 loc) • 11 kB
JavaScript
import chalk from 'chalk';
import Conf from 'conf';
import { listApps, appInfo, createApp, updateApp, deleteApp } from './commands/apps.js';
import { listSites, createSite, deleteSite, infoSite } from './commands/sites.js';
import { listFiles, makeDirectory, renameFileOrDirectory,
removeFileOrDirectory, emptyTrash, changeDirectory, showCwd,
getInfo, getDiskUsage, createFile, readFile, uploadFile,
downloadFile, copyFile, syncDirectory, editFile } from './commands/files.js';
import { getUserInfo, getUsageInfo, login } from './commands/auth.js';
import { deploy } from './commands/deploy.js';
import { PROJECT_NAME, API_BASE, getHeaders } from './commons.js';
import inquirer from 'inquirer';
import { exec } from 'node:child_process';
import { parseArgs, getSystemEditor } from './utils.js';
import { rl } from './commands/shell.js';
import { ErrorAPI } from './modules/ErrorModule.js';
const config = new Conf({ projectName: PROJECT_NAME });
// History of commands
const commandHistory = [];
/**
* Update the prompt function
* @returns The current prompt
*/
export function getPrompt() {
return chalk.cyan(`puter@${config.get('cwd').slice(1)}> `);
}
const commands = {
help: showHelp,
exit: () => process.exit(0),
logout: async () => {
await import('./commands/auth.js').then(m => m.logout());
process.exit(0);
},
login: login,
whoami: getUserInfo,
stat: getInfo,
apps: async (args) => {
await listApps({
statsPeriod: args[0] || 'all'
});
},
app: appInfo,
history: async (args) => {
const lineNumber = parseInt(args[0]);
if (isNaN(lineNumber)) {
// Display full history
commandHistory.forEach((command, index) => {
console.log(chalk.cyan(`${index + 1}: ${command}`));
});
} else {
// Copy the command at the specified line number
if (lineNumber < 1 || lineNumber > commandHistory.length) {
console.error(chalk.red(`Invalid line number. History has ${commandHistory.length} entries.`));
return;
}
const commandToCopy = commandHistory[lineNumber - 1];
// Simulate typing the command in the shell
rl.write(commandToCopy);
}
},
'last-error': async (_, context) => {
context[ErrorAPI].showLast();
},
'app:create': async (rawArgs) => {
try {
const args = parseArgs(rawArgs.join(' '));
// Consider using explicit argument definition if necessary
// const args = parseArgs(rawArgs.join(' '), {string: ['description', 'url'],
// alias: { d: 'description', u: 'url', },
// });
// NOTE: Keep the check for now at the function level, move the check here so in the future we'll use the function for non-interactive command mode.
await createApp({
name: args._[0],
directory: args._[1] || '',
description: args.description || '',
url: args.url || 'https://dev-center.puter.com/coming-soon.html'
});
} catch (error) {
console.error(chalk.red(error.message));
}
},
'app:update': async (args) => {
if (args.length < 1) {
console.log(chalk.red('Usage: app:update <name> <remote_dir>'));
return;
}
await updateApp(args);
},
'app:delete': async (rawArgs) => {
const args = parseArgs(rawArgs.join(' '), {
string: ['_'],
boolean: ['f'],
configuration: {
'populate--': true
}
});
if (args._.length < 1) {
console.log(chalk.red('You must specify the app name:'));
console.log(chalk.yellow('Example: app:delete <name>'));
return;
}
const name = args._[0];
const force = !!args.f;
if (!force){
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: chalk.yellow(`Are you sure you want to delete "${name}"?`),
default: false
}
]);
if (!confirm) {
console.log(chalk.yellow('Operation cancelled.'));
return false;
}
}
await deleteApp(name);
},
ls: listFiles,
cd: async (args) => {
await changeDirectory(args);
},
pwd: showCwd,
mkdir: makeDirectory,
mv: renameFileOrDirectory,
rm: removeFileOrDirectory,
// rmdir: deleteFolder, // Not implemented in Puter API
clean: emptyTrash,
df: getDiskUsage,
usage: getUsageInfo,
cp: copyFile,
touch: createFile,
cat: readFile,
push: uploadFile,
pull: downloadFile,
update: syncDirectory,
edit: editFile,
sites: listSites,
site: infoSite,
'site:delete': deleteSite,
'site:create': createSite,
'site:deploy': deploy,
};
/**
* Execute a command
* @param {string} input The command line input
*/
export async function execCommand(context, input) {
const [cmd, ...args] = input.split(' ');
// Add the command to history (skip the "history" command itself)
if (cmd !== 'history') {
commandHistory.push(input);
}
if (cmd === 'help') {
// Handle help command
const command = args[0];
showHelp(command);
return;
}
if (cmd.startsWith('!')) {
// Execute the command on the host machine
const hostCommand = input.slice(1); // Remove the "!"
exec(hostCommand, (error, stdout, stderr) => {
if (error) {
console.error(chalk.red(`Host Error: ${error.message}`));
return;
}
if (stderr) {
console.error(chalk.red(stderr));
return;
}
console.log(stdout);
console.log(chalk.green(`Press <Enter> to return.`));
});
return;
}
if (commands[cmd]) {
try {
await commands[cmd](args, context);
} catch (error) {
console.error(chalk.red(`Error executing command: ${error.message}`));
}
return;
}
if (!['Y', 'N'].includes(cmd.toUpperCase()[0])) {
console.log(chalk.red(`Unknown command: ${cmd}`));
showHelp();
}
}
/**
* Display help for a specific command or general help if no command is provided.
* @param {string} [command] - The command to display help for.
*/
function showHelp(command) {
// Consider using `program.helpInformation()` function for global "help" command...
const commandHelp = {
help: `
${chalk.cyan('help [command]')}
Display help for a specific command or show general help.
Example: help ls
`,
exit: `
${chalk.cyan('exit')}
Exit the shell.
`,
logout: `
${chalk.cyan('logout')}
Logout from Puter account.
`,
whoami: `
${chalk.cyan('whoami')}
Show user information.
`,
stat: `
${chalk.cyan('stat <path>')}
Show file or directory information.
Example: stat /path/to/file
`,
df: `
${chalk.cyan('df')}
Show disk usage information.
`,
usage: `
${chalk.cyan('usage')}
Show usage information.
`,
apps: `
${chalk.cyan('apps [period]')}
List all your apps.
period: today, yesterday, 7d, 30d, this_month, last_month
Example: apps today
`,
app: `
${chalk.cyan('app <app_name>')}
Get application information.
Example: app myapp
`,
'app:create': `
${chalk.cyan('app:create <name> <remote_dir>')}
Create a new app.
Example: app:create myapp https://myapp.puter.site
`,
'app:update': `
${chalk.cyan('app:update <name> [dir]')}
Update an app.
Example: app:update myapp .
`,
'app:delete': `
${chalk.cyan('app:delete <name>')}
Delete an app.
Example: app:delete myapp
`,
ls: `
${chalk.cyan('ls [dir]')}
List files and directories.
Example: ls /path/to/dir
`,
cd: `
${chalk.cyan('cd [dir]')}
Change the current working directory.
Example: cd /path/to/dir
`,
pwd: `
${chalk.cyan('pwd')}
Print the current working directory.
`,
mkdir: `
${chalk.cyan('mkdir <dir>')}
Create a new directory.
Example: mkdir /path/to/newdir
`,
mv: `
${chalk.cyan('mv <src> <dest>')}
Move or rename a file or directory.
Example: mv /path/to/src /path/to/dest
`,
rm: `
${chalk.cyan('rm <file>')}
Move a file or directory to the system's Trash.
Example: rm /path/to/file
`,
clean: `
${chalk.cyan('clean')}
Empty the system's Trash.
`,
cp: `
${chalk.cyan('cp <src> <dest>')}
Copy files or directories.
Example: cp /path/to/src /path/to/dest
`,
touch: `
${chalk.cyan('touch <file>')}
Create a new empty file.
Example: touch /path/to/file
`,
cat: `
${chalk.cyan('cat <file>')}
Output file content to the console.
Example: cat /path/to/file
`,
push: `
${chalk.cyan('push <file>')}
Upload file to Puter cloud.
Example: push /path/to/file
`,
pull: `
${chalk.cyan('pull <file>')}
Download file from Puter cloud.
Example: pull /path/to/file
`,
update: `
${chalk.cyan('update <src> <dest> [--delete] [-r]')}
Sync local directory with remote cloud.
Example: update /local/path /remote/path
`,
edit: `
${chalk.cyan('edit <file>')}
Edit a remote file using your local text editor.
Example: edit /path/to/file
System editor: ${chalk.green(getSystemEditor())}
`,
sites: `
${chalk.cyan('sites')}
List sites and subdomains.
`,
site: `
${chalk.cyan('site <site_uid>')}
Get site information by UID.
Example: site sd-123456
`,
'site:delete': `
${chalk.cyan('site:delete <uid>')}
Delete a site by UID.
Example: site:delete sd-123456
`,
'site:create': `
${chalk.cyan('site:create <app_name> [<dir>] [--subdomain=<name>]')}
Create a static website from directory.
Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
`,
'site:deploy': `
${chalk.cyan('site:deploy [<valid_name_app>] [<remote_dir>] [--subdomain=<subdomain>]')}
Deploy a local web project to Puter.
Example: site:deploy my-app ./my-app --subdomain my-app
`,
'!': `
${chalk.cyan('!<command>')}
Execute a command on the host machine.
Example: !ls -la
`,
'history [line]': `
${chalk.cyan('history [line]')}
Display history of commands or copy command by line number
Example: history 2
`,
};
if (command && commandHelp[command]) {
console.log(chalk.yellow(`\nHelp for command: ${chalk.cyan(command)}`));
console.log(commandHelp[command]);
} else if (command) {
console.log(chalk.red(`Unknown command: ${command}`));
console.log(chalk.yellow('Use "help" to see a list of available commands.'));
} else {
console.log(chalk.yellow('\nAvailable commands:'));
for (const cmd in commandHelp) {
console.log(chalk.cyan(cmd.padEnd(20)) + '- ' + commandHelp[cmd].split('\n')[2].trim());
}
console.log(chalk.yellow('\nUse "help <command>" for detailed help on a specific command.'));
}
}