@heyputer/shell 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +9 -0
- package/.env.example +3 -0
- package/.github/workflows/npm-build.yml +34 -0
- package/.github/workflows/npm-publish.yml +50 -0
- package/.idx/dev.nix +53 -0
- package/CHANGELOG.md +292 -0
- package/LICENSE.md +21 -0
- package/README.md +18 -0
- package/bin/index.js +258 -0
- package/package.json +58 -0
- package/screenshot.png +0 -0
- package/src/commands/apps.js +356 -0
- package/src/commands/auth.js +195 -0
- package/src/commands/deploy.js +54 -0
- package/src/commands/files.js +1187 -0
- package/src/commands/init.js +322 -0
- package/src/commands/shell.js +61 -0
- package/src/commands/sites.js +169 -0
- package/src/commands/subdomains.js +95 -0
- package/src/commons.js +409 -0
- package/src/crypto.js +9 -0
- package/src/executor.js +386 -0
- package/src/modules/ErrorModule.js +20 -0
- package/src/modules/ProfileModule.js +334 -0
- package/src/modules/PuterModule.js +30 -0
- package/src/utils.js +135 -0
- package/tests/ErrorModule.test.js +42 -0
- package/tests/ProfileModule.test.js +274 -0
- package/tests/PuterModule.test.js +56 -0
- package/tests/apps.test.js +194 -0
- package/tests/commons.test.js +380 -0
- package/tests/deploy.test.js +84 -0
- package/tests/executor.test.js +52 -0
- package/tests/files.test.js +640 -0
- package/tests/login.test.js +206 -0
- package/tests/shell.test.js +184 -0
- package/tests/sites.test.js +67 -0
- package/tests/subdomains.test.js +90 -0
- package/tests/utils.test.js +193 -0
package/src/executor.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Conf from 'conf';
|
|
3
|
+
import { listApps, appInfo, createApp, updateApp, deleteApp } from './commands/apps.js';
|
|
4
|
+
import { listSites, createSite, deleteSite, infoSite } from './commands/sites.js';
|
|
5
|
+
import {
|
|
6
|
+
listFiles, makeDirectory, renameFileOrDirectory,
|
|
7
|
+
removeFileOrDirectory, emptyTrash, changeDirectory, showCwd,
|
|
8
|
+
getInfo, getDiskUsage, createFile, readFile, uploadFile,
|
|
9
|
+
downloadFile, copyFile, syncDirectory, editFile
|
|
10
|
+
} from './commands/files.js';
|
|
11
|
+
import { getUserInfo, getUsageInfo, login } from './commands/auth.js';
|
|
12
|
+
import { deploy } from './commands/deploy.js';
|
|
13
|
+
import { PROJECT_NAME, API_BASE, getHeaders } from './commons.js';
|
|
14
|
+
import inquirer from 'inquirer';
|
|
15
|
+
import { exec } from 'node:child_process';
|
|
16
|
+
import { parseArgs, getSystemEditor } from './utils.js';
|
|
17
|
+
import { rl } from './commands/shell.js';
|
|
18
|
+
import { showLast } from './modules/ErrorModule.js'
|
|
19
|
+
|
|
20
|
+
const config = new Conf({ projectName: PROJECT_NAME });
|
|
21
|
+
|
|
22
|
+
// History of commands
|
|
23
|
+
const commandHistory = [];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Update the prompt function
|
|
27
|
+
* @returns The current prompt
|
|
28
|
+
*/
|
|
29
|
+
export function getPrompt() {
|
|
30
|
+
return chalk.cyan(`puter@${config.get('cwd').slice(1)}> `);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const commands = {
|
|
34
|
+
help: showHelp,
|
|
35
|
+
exit: () => process.exit(0),
|
|
36
|
+
logout: async () => {
|
|
37
|
+
await import('./commands/auth.js').then(m => m.logout());
|
|
38
|
+
process.exit(0);
|
|
39
|
+
},
|
|
40
|
+
login: login,
|
|
41
|
+
whoami: getUserInfo,
|
|
42
|
+
stat: getInfo,
|
|
43
|
+
apps: async (args) => {
|
|
44
|
+
await listApps({
|
|
45
|
+
statsPeriod: args[0] || 'all'
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
app: appInfo,
|
|
49
|
+
history: async (args) => {
|
|
50
|
+
const lineNumber = parseInt(args[0]);
|
|
51
|
+
|
|
52
|
+
if (isNaN(lineNumber)) {
|
|
53
|
+
// Display full history
|
|
54
|
+
commandHistory.forEach((command, index) => {
|
|
55
|
+
console.log(chalk.cyan(`${index + 1}: ${command}`));
|
|
56
|
+
});
|
|
57
|
+
} else {
|
|
58
|
+
// Copy the command at the specified line number
|
|
59
|
+
if (lineNumber < 1 || lineNumber > commandHistory.length) {
|
|
60
|
+
console.error(chalk.red(`Invalid line number. History has ${commandHistory.length} entries.`));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const commandToCopy = commandHistory[lineNumber - 1];
|
|
65
|
+
// Simulate typing the command in the shell
|
|
66
|
+
rl.write(commandToCopy);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
'last-error': showLast,
|
|
70
|
+
'app:create': async (rawArgs) => {
|
|
71
|
+
try {
|
|
72
|
+
const args = parseArgs(rawArgs.join(' '));
|
|
73
|
+
// Consider using explicit argument definition if necessary
|
|
74
|
+
// const args = parseArgs(rawArgs.join(' '), {string: ['description', 'url'],
|
|
75
|
+
// alias: { d: 'description', u: 'url', },
|
|
76
|
+
// });
|
|
77
|
+
|
|
78
|
+
// 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.
|
|
79
|
+
await createApp({
|
|
80
|
+
name: args._[0],
|
|
81
|
+
directory: args._[1] || '',
|
|
82
|
+
description: args.description || '',
|
|
83
|
+
url: args.url || 'https://dev-center.puter.com/coming-soon.html'
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error(chalk.red(error.message));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
'app:update': async (args) => {
|
|
90
|
+
if (args.length < 1) {
|
|
91
|
+
console.log(chalk.red('Usage: app:update <name> <remote_dir>'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await updateApp(args);
|
|
95
|
+
},
|
|
96
|
+
'app:delete': async (rawArgs) => {
|
|
97
|
+
const args = parseArgs(rawArgs.join(' '), {
|
|
98
|
+
string: ['_'],
|
|
99
|
+
boolean: ['f'],
|
|
100
|
+
configuration: {
|
|
101
|
+
'populate--': true
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (args._.length < 1) {
|
|
105
|
+
console.log(chalk.red('You must specify the app name:'));
|
|
106
|
+
console.log(chalk.yellow('Example: app:delete <name>'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const name = args._[0];
|
|
110
|
+
const force = !!args.f;
|
|
111
|
+
|
|
112
|
+
if (!force) {
|
|
113
|
+
const { confirm } = await inquirer.prompt([
|
|
114
|
+
{
|
|
115
|
+
type: 'confirm',
|
|
116
|
+
name: 'confirm',
|
|
117
|
+
message: chalk.yellow(`Are you sure you want to delete "${name}"?`),
|
|
118
|
+
default: false
|
|
119
|
+
}
|
|
120
|
+
]);
|
|
121
|
+
if (!confirm) {
|
|
122
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await deleteApp(name);
|
|
127
|
+
},
|
|
128
|
+
ls: listFiles,
|
|
129
|
+
cd: async (args) => {
|
|
130
|
+
await changeDirectory(args);
|
|
131
|
+
},
|
|
132
|
+
pwd: showCwd,
|
|
133
|
+
mkdir: makeDirectory,
|
|
134
|
+
mv: renameFileOrDirectory,
|
|
135
|
+
rm: removeFileOrDirectory,
|
|
136
|
+
// rmdir: deleteFolder, // Not implemented in Puter API
|
|
137
|
+
clean: emptyTrash,
|
|
138
|
+
df: getDiskUsage,
|
|
139
|
+
usage: getUsageInfo,
|
|
140
|
+
cp: copyFile,
|
|
141
|
+
touch: createFile,
|
|
142
|
+
cat: readFile,
|
|
143
|
+
push: uploadFile,
|
|
144
|
+
pull: downloadFile,
|
|
145
|
+
update: syncDirectory,
|
|
146
|
+
edit: editFile,
|
|
147
|
+
sites: listSites,
|
|
148
|
+
site: infoSite,
|
|
149
|
+
'site:delete': deleteSite,
|
|
150
|
+
'site:create': createSite,
|
|
151
|
+
'site:deploy': deploy,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Execute a command
|
|
156
|
+
* @param {string} input The command line input
|
|
157
|
+
*/
|
|
158
|
+
export async function execCommand(input) {
|
|
159
|
+
const [cmd, ...args] = input ? input.split(' ') : [];
|
|
160
|
+
|
|
161
|
+
// Add the command to history (skip the "history" command itself)
|
|
162
|
+
if (cmd !== 'history') {
|
|
163
|
+
commandHistory.push(input);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (cmd === 'help') {
|
|
167
|
+
// Handle help command
|
|
168
|
+
const command = args[0];
|
|
169
|
+
showHelp(command);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (cmd.startsWith('!')) {
|
|
173
|
+
// Execute the command on the host machine
|
|
174
|
+
const hostCommand = input.slice(1); // Remove the "!"
|
|
175
|
+
exec(hostCommand, (error, stdout, stderr) => {
|
|
176
|
+
if (error) {
|
|
177
|
+
console.error(chalk.red(`Host Error: ${error.message}`));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (stderr) {
|
|
181
|
+
console.error(chalk.red(stderr));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.log(stdout);
|
|
185
|
+
console.log(chalk.green(`Press <Enter> to return.`));
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (commands[cmd]) {
|
|
190
|
+
try {
|
|
191
|
+
await commands[cmd](args);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(chalk.red(`Error executing command: ${error.message}`));
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!['Y', 'N'].includes(cmd.toUpperCase()[0])) {
|
|
199
|
+
console.log(chalk.red(`Unknown command: ${cmd}`));
|
|
200
|
+
showHelp();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Display help for a specific command or general help if no command is provided.
|
|
206
|
+
* @param {string} [command] - The command to display help for.
|
|
207
|
+
*/
|
|
208
|
+
function showHelp(command) {
|
|
209
|
+
// Consider using `program.helpInformation()` function for global "help" command...
|
|
210
|
+
const commandHelp = {
|
|
211
|
+
help: `
|
|
212
|
+
${chalk.cyan('help [command]')}
|
|
213
|
+
Display help for a specific command or show general help.
|
|
214
|
+
Example: help ls
|
|
215
|
+
`,
|
|
216
|
+
exit: `
|
|
217
|
+
${chalk.cyan('exit')}
|
|
218
|
+
Exit the shell.
|
|
219
|
+
`,
|
|
220
|
+
logout: `
|
|
221
|
+
${chalk.cyan('logout')}
|
|
222
|
+
Logout from Puter account.
|
|
223
|
+
`,
|
|
224
|
+
whoami: `
|
|
225
|
+
${chalk.cyan('whoami')}
|
|
226
|
+
Show user information.
|
|
227
|
+
`,
|
|
228
|
+
stat: `
|
|
229
|
+
${chalk.cyan('stat <path>')}
|
|
230
|
+
Show file or directory information.
|
|
231
|
+
Example: stat /path/to/file
|
|
232
|
+
`,
|
|
233
|
+
df: `
|
|
234
|
+
${chalk.cyan('df')}
|
|
235
|
+
Show disk usage information.
|
|
236
|
+
`,
|
|
237
|
+
usage: `
|
|
238
|
+
${chalk.cyan('usage')}
|
|
239
|
+
Show usage information.
|
|
240
|
+
`,
|
|
241
|
+
apps: `
|
|
242
|
+
${chalk.cyan('apps [period]')}
|
|
243
|
+
List all your apps.
|
|
244
|
+
period: today, yesterday, 7d, 30d, this_month, last_month
|
|
245
|
+
Example: apps today
|
|
246
|
+
`,
|
|
247
|
+
app: `
|
|
248
|
+
${chalk.cyan('app <app_name>')}
|
|
249
|
+
Get application information.
|
|
250
|
+
Example: app myapp
|
|
251
|
+
`,
|
|
252
|
+
'app:create': `
|
|
253
|
+
${chalk.cyan('app:create <name> <remote_dir>')}
|
|
254
|
+
Create a new app.
|
|
255
|
+
Example: app:create myapp https://myapp.puter.site
|
|
256
|
+
`,
|
|
257
|
+
'app:update': `
|
|
258
|
+
${chalk.cyan('app:update <name> [dir]')}
|
|
259
|
+
Update an app.
|
|
260
|
+
Example: app:update myapp .
|
|
261
|
+
`,
|
|
262
|
+
'app:delete': `
|
|
263
|
+
${chalk.cyan('app:delete <name>')}
|
|
264
|
+
Delete an app.
|
|
265
|
+
Example: app:delete myapp
|
|
266
|
+
`,
|
|
267
|
+
ls: `
|
|
268
|
+
${chalk.cyan('ls [dir]')}
|
|
269
|
+
List files and directories.
|
|
270
|
+
Example: ls /path/to/dir
|
|
271
|
+
`,
|
|
272
|
+
cd: `
|
|
273
|
+
${chalk.cyan('cd [dir]')}
|
|
274
|
+
Change the current working directory.
|
|
275
|
+
Example: cd /path/to/dir
|
|
276
|
+
`,
|
|
277
|
+
pwd: `
|
|
278
|
+
${chalk.cyan('pwd')}
|
|
279
|
+
Print the current working directory.
|
|
280
|
+
`,
|
|
281
|
+
mkdir: `
|
|
282
|
+
${chalk.cyan('mkdir <dir>')}
|
|
283
|
+
Create a new directory.
|
|
284
|
+
Example: mkdir /path/to/newdir
|
|
285
|
+
`,
|
|
286
|
+
mv: `
|
|
287
|
+
${chalk.cyan('mv <src> <dest>')}
|
|
288
|
+
Move or rename a file or directory.
|
|
289
|
+
Example: mv /path/to/src /path/to/dest
|
|
290
|
+
`,
|
|
291
|
+
rm: `
|
|
292
|
+
${chalk.cyan('rm <file>')}
|
|
293
|
+
Move a file or directory to the system's Trash.
|
|
294
|
+
Example: rm /path/to/file
|
|
295
|
+
`,
|
|
296
|
+
clean: `
|
|
297
|
+
${chalk.cyan('clean')}
|
|
298
|
+
Empty the system's Trash.
|
|
299
|
+
`,
|
|
300
|
+
cp: `
|
|
301
|
+
${chalk.cyan('cp <src> <dest>')}
|
|
302
|
+
Copy files or directories.
|
|
303
|
+
Example: cp /path/to/src /path/to/dest
|
|
304
|
+
`,
|
|
305
|
+
touch: `
|
|
306
|
+
${chalk.cyan('touch <file>')}
|
|
307
|
+
Create a new empty file.
|
|
308
|
+
Example: touch /path/to/file
|
|
309
|
+
`,
|
|
310
|
+
cat: `
|
|
311
|
+
${chalk.cyan('cat <file>')}
|
|
312
|
+
Output file content to the console.
|
|
313
|
+
Example: cat /path/to/file
|
|
314
|
+
`,
|
|
315
|
+
push: `
|
|
316
|
+
${chalk.cyan('push <file>')}
|
|
317
|
+
Upload file to Puter cloud.
|
|
318
|
+
Example: push /path/to/file
|
|
319
|
+
`,
|
|
320
|
+
pull: `
|
|
321
|
+
${chalk.cyan('pull <file>')}
|
|
322
|
+
Download file from Puter cloud.
|
|
323
|
+
Example: pull /path/to/file
|
|
324
|
+
`,
|
|
325
|
+
update: `
|
|
326
|
+
${chalk.cyan('update <src> <dest> [--delete] [-r]')}
|
|
327
|
+
Sync local directory with remote cloud.
|
|
328
|
+
Example: update /local/path /remote/path
|
|
329
|
+
`,
|
|
330
|
+
edit: `
|
|
331
|
+
${chalk.cyan('edit <file>')}
|
|
332
|
+
Edit a remote file using your local text editor.
|
|
333
|
+
Example: edit /path/to/file
|
|
334
|
+
|
|
335
|
+
System editor: ${chalk.green(getSystemEditor())}
|
|
336
|
+
`,
|
|
337
|
+
sites: `
|
|
338
|
+
${chalk.cyan('sites')}
|
|
339
|
+
List sites and subdomains.
|
|
340
|
+
`,
|
|
341
|
+
site: `
|
|
342
|
+
${chalk.cyan('site <site_uid>')}
|
|
343
|
+
Get site information by UID.
|
|
344
|
+
Example: site sd-123456
|
|
345
|
+
`,
|
|
346
|
+
'site:delete': `
|
|
347
|
+
${chalk.cyan('site:delete <uid>')}
|
|
348
|
+
Delete a site by UID.
|
|
349
|
+
Example: site:delete sd-123456
|
|
350
|
+
`,
|
|
351
|
+
'site:create': `
|
|
352
|
+
${chalk.cyan('site:create <app_name> [<dir>] [--subdomain=<name>]')}
|
|
353
|
+
Create a static website from directory.
|
|
354
|
+
Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
|
|
355
|
+
`,
|
|
356
|
+
'site:deploy': `
|
|
357
|
+
${chalk.cyan('site:deploy [<remote_dir>] [--subdomain=<subdomain>]')}
|
|
358
|
+
Deploy a local web project to Puter.
|
|
359
|
+
Example: site:deploy ./my-app --subdomain my-app
|
|
360
|
+
`,
|
|
361
|
+
'!': `
|
|
362
|
+
${chalk.cyan('!<command>')}
|
|
363
|
+
Execute a command on the host machine.
|
|
364
|
+
Example: !ls -la
|
|
365
|
+
`,
|
|
366
|
+
'history [line]': `
|
|
367
|
+
${chalk.cyan('history [line]')}
|
|
368
|
+
Display history of commands or copy command by line number
|
|
369
|
+
Example: history 2
|
|
370
|
+
`,
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
if (command && commandHelp[command]) {
|
|
374
|
+
console.log(chalk.yellow(`\nHelp for command: ${chalk.cyan(command)}`));
|
|
375
|
+
console.log(commandHelp[command]);
|
|
376
|
+
} else if (command) {
|
|
377
|
+
console.log(chalk.red(`Unknown command: ${command}`));
|
|
378
|
+
console.log(chalk.yellow('Use "help" to see a list of available commands.'));
|
|
379
|
+
} else {
|
|
380
|
+
console.log(chalk.yellow('\nAvailable commands:'));
|
|
381
|
+
for (const cmd in commandHelp) {
|
|
382
|
+
console.log(chalk.cyan(cmd.padEnd(20)) + '- ' + commandHelp[cmd].split('\n')[2].trim());
|
|
383
|
+
}
|
|
384
|
+
console.log(chalk.yellow('\nUse "help <command>" for detailed help on a specific command.'));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const ERROR_BUFFER_LIMIT = 20;
|
|
2
|
+
|
|
3
|
+
export const errors = [];
|
|
4
|
+
|
|
5
|
+
export const report = (error) => {
|
|
6
|
+
errors.push(error);
|
|
7
|
+
if (errors.length > ERROR_BUFFER_LIMIT) {
|
|
8
|
+
errors.splice(0, errors.length - ERROR_BUFFER_LIMIT)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export const showLast = () => {
|
|
12
|
+
// Print the last error from the error history,
|
|
13
|
+
// and remove it from the history
|
|
14
|
+
const err = errors.pop();
|
|
15
|
+
if (err) {
|
|
16
|
+
console.error(err);
|
|
17
|
+
} else {
|
|
18
|
+
console.log('No errors to report');
|
|
19
|
+
}
|
|
20
|
+
}
|