Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/npm-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ jobs:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ jobs:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
Expand All @@ -34,8 +32,6 @@ jobs:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Use Node.js 20
uses: actions/setup-node@v4
with:
Expand Down
224 changes: 22 additions & 202 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@
import { Command } from 'commander';
import chalk from 'chalk';
import { login, logout } from '../src/commands/auth.js';
import { init } from '../src/commands/init.js';
import { startShell } from '../src/commands/shell.js';
import { PROJECT_NAME, getLatestVersion } from '../src/commons.js';
import { appInfo, createApp, listApps, deleteApp, updateApp } from '../src/commands/apps.js';
import inquirer from 'inquirer';
import { initProfileModule, getProfileModule } from '../src/modules/ProfileModule.js';
import { initProfileModule } from '../src/modules/ProfileModule.js';
import { initPuterModule } from '../src/modules/PuterModule.js';
import { createSite, infoSite, listSites, deleteSite } from '../src/commands/sites.js';
import { formatError, isAuthError, report } from '../src/modules/ErrorModule.js';

// puter.js attaches an async 'load' listener to its XHRs, so a failed request
// rejects twice: once on the promise we await, and once on a promise nothing
// holds. Without this guard Node kills the CLI and prints the rejected plain
// object as "#<Object>". Auth failures are already surfaced with a re-login
// prompt by checkLogin(), so only report what is not handled elsewhere.
process.on('unhandledRejection', (error) => {
report(error);
if (!isAuthError(error)) {
console.error(chalk.red(`Error: ${formatError(error)}`));
console.error(chalk.dim('Run "last-error" in the shell for the full details.'));
}
});

async function main() {
initProfileModule();
initPuterModule();

const profileModule = getProfileModule();

const version = await getLatestVersion(PROJECT_NAME);

const program = new Command();
Expand Down Expand Up @@ -45,206 +53,12 @@ async function main() {
process.exit(0);
});

program
.command('init')
.description('Initialize a new Puter app')
.action(init);

program
.command('shell')
.description('Start interactive shell')
.action(() => startShell());


// App commands
program
.command('apps')
.description('List all your apps')
.argument('[period]', 'period: today, yesterday, 7d, 30d, this_month, last_month')
.action(async (period) => {
await profileModule.checkLogin();
await listApps({
statsPeriod: period || 'all'
});
process.exit(0);
});

const app = program
.command('app')
.description('App management commands');

app
.command('info')
.description('Get application information')
.argument('<app_name>', 'Name of the application')
.action(async (app_name) => {
await profileModule.checkLogin();
await appInfo([app_name]);
process.exit(0);
});

app
.command('create')
.description('Create a new app')
.argument('<name>', 'Name of the application')
.argument('<remote_dir>', 'Remote directory URL')
.action(async (name, remote_dir) => {
try {
await profileModule.checkLogin();
await createApp({
name: name,
directory: remote_dir || '',
description: '',
url: 'https://dev-center.puter.com/coming-soon.html'
});
} catch (error) {
console.error(chalk.red(error.message));
}
process.exit(0);
});

app
.command('update')
.description('Update an app')
.argument('<name>', 'Name of the application')
.argument('[dir]', 'Directory path', '.')
.action(async (name, dir) => {
await profileModule.checkLogin();
await updateApp([name, dir]);
process.exit(0);
});

app
.command('delete')
.description('Delete an app')
.argument('<name>', 'Name of the application')
.option('-f, --force', 'Force deletion without confirmation')
.action(async (name, options) => {
await profileModule.checkLogin();
let shouldDelete = options.force;

if (!shouldDelete) {
const answer = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to delete the app "${name}"?`,
default: false
}
]);
shouldDelete = answer.confirm;
}

if (shouldDelete) {
await deleteApp(name);
} else {
console.log(chalk.yellow('App deletion cancelled.'));
}
process.exit(0);
});

program
.command('sites')
.description('List sites and subdomains')
.action(async () => {
await profileModule.checkLogin();
await listSites();
process.exit(0);
});

const site = program
.command('site')
.description('Site management commands');

site
.command('info')
.description('Get site information by UID')
.argument('<site_uid>', 'Site UID')
.action(async (site_uid) => {
await profileModule.checkLogin();
await infoSite([site_uid]);
process.exit(0);
});

site
.command('create')
.description('Create a static website from directory')
.argument('<app_name>', 'Application name')
.argument('[dir]', 'Directory path')
.option('--subdomain <name>', 'Subdomain name')
.action(async (app_name, dir, options) => {
await profileModule.checkLogin();
const args = [app_name];
if (dir) args.push(dir);
if (options.subdomain) args.push(`--subdomain=${options.subdomain}`)

await createSite(args)
process.exit(0);
});

site
.command('deploy')
.description('Deploy a local web project to Puter')
.argument('[local_dir]', 'Local directory path')
.argument('[subdomain]', 'Deployment subdomain (<subdomain>.puter.site)')
.action(async (local_dir, subdomain) => {
await profileModule.checkLogin();
if (!local_dir) {
const answer = await inquirer.prompt([
{
type: 'input',
name: 'local_dir',
message: 'Local directory path:',
default: '.'
}
]);
local_dir = answer.local_dir;
}

if (!subdomain) {
const answer = await inquirer.prompt([
{
type: 'input',
name: 'subdomain',
message: 'Deployment subdomain (leave empty for random):',
}
]);
subdomain = answer.subdomain;
}

await startShell(`site:deploy ${local_dir}${subdomain ? ` --subdomain=${subdomain}` : ''}`)
process.exit(0);
});

site
.command('delete')
.description('Delete a site by UID')
.argument('<uid>', 'Site UID')
.option('-f, --force', 'Force deletion without confirmation')
.action(async (uid, options) => {
await profileModule.checkLogin();
let shouldDelete = options.force;

if (!shouldDelete) {
const answer = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `Are you sure you want to delete the site with UID "${uid}"?`,
default: false
}
]);
shouldDelete = answer.confirm;
}

if (shouldDelete) {
await deleteSite([uid]);
} else {
console.log(chalk.yellow('Site deletion cancelled.'));
}
process.exit(0);
});

if (process.argv.length === 2) {
startShell();
} else {
Expand All @@ -253,6 +67,12 @@ async function main() {
}

main().catch((err) => {
console.error(err);
report(err);
if (isAuthError(err)) {
console.error(chalk.red('Your session has expired or its token is no longer valid.'));
console.error(chalk.cyan('Run "puter login" to sign in again.'));
} else {
console.error(chalk.red(`Error: ${formatError(err)}`));
}
process.exit(1);
});
8 changes: 3 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@heyputer/shell",
"version": "2.1.0",
"version": "3.0.0",
"description": "SSH-style shell access to your Puter files",
"main": "index.js",
"bin": {
Expand All @@ -19,6 +19,7 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "pnpm@9.9.0",
"keywords": [
"puter",
"shell",
Expand All @@ -29,18 +30,15 @@
"dependencies": {
"@heyputer/puter.js": "^2.2.8",
"chalk": "^5.3.0",
"cli-table3": "^0.6.5",
"commander": "^13.0.0",
"conf": "^12.0.0",
"cross-spawn": "^7.0.3",
"dotenv": "^16.4.7",
"glob": "^11.0.0",
"inquirer": "^9.2.12",
"minimatch": "^10.0.1",
"node-fetch": "^3.3.2",
"ora": "^8.0.1",
"uuid": "^11.0.5",
"yargs-parser": "^21.1.1"
"uuid": "^11.0.5"
},
"devDependencies": {
"@vitest/coverage-v8": "2.1.8",
Expand Down
Loading
Loading