puter-cli 1.8.4 → 1.8.5

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/CHANGELOG.md CHANGED
@@ -4,8 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v1.8.5](https://github.com/HeyPuter/puter-cli/compare/v1.8.4...v1.8.5)
8
+
9
+ - feat(site): add site:deploy command for one-step project deployment [`94210ed`](https://github.com/HeyPuter/puter-cli/commit/94210ed796e7564647f4cd177a1b0bef72e66ef2)
10
+ - Fix and add some details to the README file [`8acb6ea`](https://github.com/HeyPuter/puter-cli/commit/8acb6ea753b4f83c146f26473f61756e613a4953)
11
+ - fix(files): touch command to creation a new file [`d30a067`](https://github.com/HeyPuter/puter-cli/commit/d30a0673e9aa5fb5b5d6b037a6a134a196a27529)
12
+
7
13
  #### [v1.8.4](https://github.com/HeyPuter/puter-cli/compare/v1.8.3...v1.8.4)
8
14
 
15
+ > 20 September 2025
16
+
9
17
  - fix(app): improve `app:delete` argument validation and usage [`eda41cd`](https://github.com/HeyPuter/puter-cli/commit/eda41cd7c623b8a3f0c60f586de756d4369face3)
10
18
 
11
19
  #### [v1.8.3](https://github.com/HeyPuter/puter-cli/compare/v1.8.2...v1.8.3)
package/README.md CHANGED
@@ -141,14 +141,9 @@ P.S. The `--delete` flag removes files in the remote directory that don't exist
141
141
  ```
142
142
  P.S. This command will download the remote file to your local machine, open it in your default editor, and then upload the changes back to the remote instance. It uses `vim` by default, but you can change it by setting the `EDITOR` environment variable.
143
143
 
144
- #### User Information
145
- ```
146
-
147
- The addition describes the `update` command which allows for bidirectional synchronization between local and remote directories, including the optional flags for deleting files and recursive synchronization.
148
- ---
149
-
150
144
 
151
145
  #### User Information
146
+
152
147
  - **Get User Info**: Display user information.
153
148
  ```bash
154
149
  puter> whoami
@@ -314,10 +309,10 @@ cp .env.example .env
314
309
 
315
310
  ## Known issues:
316
311
 
317
- Most features are working fine. If you have any issues with this project or the Puter SDK, please let us know:
312
+ Most features are working fine. If you have any issues with this project, please let us know:
318
313
 
319
314
  ## Interactive Shell prompt:
320
- If you want to stay in the interactive shell you should provide "-f" (aka: force delete) argument, when want to delete any object:
315
+ If you want to stay in the interactive shell you should provide "-f" (aka: force delete) argument, when you want to delete any object:
321
316
  ```bash
322
317
  puter@username/myapp> rm README.md
323
318
  The following items will be moved to Trash:
@@ -334,13 +329,14 @@ Otherwise, the Interactive Shell mode will be terminated.
334
329
 
335
330
  ## Notes
336
331
 
337
- This project is not equivalent [phoenix](https://github.com/HeyPuter/puter/blob/main/src/phoenix/README.md), neither an attempt to mimic some of its features, it's rather a CLI tool to do most the Puter's API from the command line.
332
+ This project is not equivalent [phoenix](https://github.com/HeyPuter/puter/blob/main/src/phoenix/README.md), neither an attempt to mimic some of its features, it's rather a CLI tool to do most the Puter's API actions from the command line.
338
333
 
339
334
  ---
340
335
 
341
336
  ## Configuration
342
337
 
343
338
  The CLI uses a configuration file to store user credentials and settings. You can use the `puter logout` to clear the configuration settings.
339
+ You can create a `.env` file to use your own settings for `PUTER_API_BASE` and `PUTER_BASE_URL` in order to work on Puter locally for development purposes.
344
340
 
345
341
  ---
346
342
 
package/bin/index.js CHANGED
@@ -6,6 +6,7 @@ import { init } from '../src/commands/init.js';
6
6
  import { startShell } from '../src/commands/shell.js';
7
7
  import { PROJECT_NAME, getLatestVersion } from '../src/commons.js';
8
8
  import { createApp } from '../src/commands/apps.js';
9
+ import { deploy } from '../src/commands/deploy.js';
9
10
 
10
11
  async function main() {
11
12
  const version = await getLatestVersion(PROJECT_NAME);
@@ -62,6 +63,23 @@ async function main() {
62
63
  process.exit(0);
63
64
  });
64
65
 
66
+ /*/ Deploy command
67
+ program
68
+ .command('site:deploy')
69
+ .description('Deploy a local web project to Puter')
70
+ .argument('[name]', 'Name of the site')
71
+ .argument('[remoteDir]', 'Remote directory path')
72
+ .option('--subdomain <subdomain>', 'Subdomain for the site')
73
+ .action(async (name, remoteDir, options) => {
74
+ try {
75
+ await deploy([name, remoteDir, `--subdomain=${options.subdomain}`].filter(Boolean));
76
+ } catch (error) {
77
+ console.error(chalk.red(error.message));
78
+ }
79
+ process.exit(0);
80
+ });
81
+ */
82
+
65
83
  if (process.argv.length === 2) {
66
84
  startShell();
67
85
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puter-cli",
3
- "version": "1.8.4",
3
+ "version": "1.8.5",
4
4
  "description": "Command line interface for Puter cloud platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,52 @@
1
+ import chalk from 'chalk';
2
+ import { generateAppName, isValidAppName } from '../commons.js';
3
+ import { syncDirectory } from './files.js';
4
+ import { createSite } from './sites.js';
5
+ import { getCurrentDirectory } from './auth.js';
6
+ import { getSubdomains } from './subdomains.js';
7
+
8
+ /**
9
+ * Deploy a local web project to Puter.
10
+ * @param {string[]} args - Command-line arguments (e.g., <valid_site_app> [<remote_dir>] [--subdomain=<subdomain>]).
11
+ */
12
+ export async function deploy(args = []) {
13
+ if (args.length < 1 || !isValidAppName(args[0])) {
14
+ console.log(chalk.red('Usage: site:deploy <valid_site_app> [<remote_dir>] [--subdomain=<subdomain>]'));
15
+ console.log(chalk.yellow('Example: site:deploy'));
16
+ console.log(chalk.yellow('Example: site:deploy my-app'));
17
+ console.log(chalk.yellow('Example: site:deploy my-app ./my-app'));
18
+ console.log(chalk.yellow('Example: site:deploy my-app ./my-app --subdomain=my-app-new'));
19
+ return;
20
+ }
21
+ const appName = args[0]; // && !args[0].startsWith('--') ? args[0] : generateAppName();
22
+ const remoteDirArg = args.find(arg => !arg.startsWith('--') && arg !== appName);
23
+ const remoteDir = remoteDirArg || '.'; // `./${appName}`;
24
+ const subdomain = args.find(arg => arg.startsWith('--subdomain='))?.split('=')[1] || appName;
25
+ const sourceDir = '.'; // Deploy from the current directory
26
+
27
+ console.log(chalk.cyan(`Deploying '${appName}' from local '${sourceDir}' to remote '${remoteDir}' at '${subdomain}.puter.site'...`));
28
+
29
+ try {
30
+ // 1. Upload files
31
+ console.log(chalk.cyan(`Uploading files from '${sourceDir}' to '${remoteDir}'...`));
32
+ await syncDirectory([sourceDir, remoteDir, '--delete', '-r', '--overwrite']);
33
+
34
+ // 2. Create the site
35
+ console.log(chalk.cyan(`Creating site...`));
36
+ const site = await createSite([appName, remoteDir, `--subdomain=${subdomain}`]);
37
+
38
+ if (site) {
39
+ console.log(chalk.green('Deployment successful!'));
40
+ } else {
41
+ console.log(chalk.yellow('Deployment successfuly updated!'));
42
+ }
43
+ if (subdomain){
44
+ console.log(chalk.cyan('Your site is live at:'));
45
+ console.log(chalk.green(`https://${subdomain}.puter.site`));
46
+ } else {
47
+ console.log(chalk.red('Deployment failed. Subdomain cannot be reserved!'));
48
+ }
49
+ } catch (error) {
50
+ console.error(chalk.red(`Deployment failed: ${error.message}`));
51
+ }
52
+ }
@@ -7,7 +7,7 @@ import { minimatch } from 'minimatch';
7
7
  import chalk from 'chalk';
8
8
  import Conf from 'conf';
9
9
  import fetch from 'node-fetch';
10
- import { API_BASE, BASE_URL, PROJECT_NAME, getHeaders, showDiskSpaceUsage, resolvePath } from '../commons.js';
10
+ import { API_BASE, BASE_URL, PROJECT_NAME, getHeaders, showDiskSpaceUsage, resolvePath, resolveRemotePath } from '../commons.js';
11
11
  import { formatDateTime, formatSize, getSystemEditor } from '../utils.js';
12
12
  import inquirer from 'inquirer';
13
13
  import { getAuthToken, getCurrentDirectory, getCurrentUserName } from './auth.js';
@@ -626,7 +626,9 @@ export async function createFile(args = []) {
626
626
  })
627
627
  });
628
628
 
629
- if (statResponse.ok) {
629
+ if (!statResponse.ok) {
630
+ console.log(chalk.cyan('File does not exists. Il will be created.'));
631
+ } else {
630
632
  const statData = await statResponse.json();
631
633
  if (statData && statData.id) {
632
634
  if (!overwrite) {
@@ -635,9 +637,6 @@ export async function createFile(args = []) {
635
637
  }
636
638
  console.log(chalk.yellow(`File "${filePath}" already exists. It will be overwritten.`));
637
639
  }
638
- } else if (statResponse.status !== 404) {
639
- console.error(chalk.red('Failed to check if file exists.'));
640
- return false;
641
640
  }
642
641
 
643
642
  // Step 2: Check disk space
@@ -1263,7 +1262,7 @@ async function ensureRemoteDirectoryExists(remotePath) {
1263
1262
  * @param {string[]} args - Command-line arguments (e.g., [localDir, remoteDir, --delete, -r]).
1264
1263
  */
1265
1264
  export async function syncDirectory(args = []) {
1266
- const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete] [-r]';
1265
+ const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete] [-r] [--overwrite]';
1267
1266
  if (args.length < 2) {
1268
1267
  console.log(chalk.red(usageMessage));
1269
1268
  return;
@@ -1273,11 +1272,13 @@ export async function syncDirectory(args = []) {
1273
1272
  let remoteDir = '';
1274
1273
  let deleteFlag = '';
1275
1274
  let recursiveFlag = false;
1275
+ let overwriteFlag = false;
1276
1276
  try {
1277
1277
  localDir = await resolveLocalDirectory(args[0]);
1278
- remoteDir = resolvePath(getCurrentDirectory(), args[1]);
1278
+ remoteDir = resolveRemotePath(getCurrentDirectory(), args[1]);
1279
1279
  deleteFlag = args.includes('--delete'); // Whether to delete extra files
1280
1280
  recursiveFlag = args.includes('-r'); // Whether to recursively process subdirectories
1281
+ overwriteFlag = args.includes('--overwrite');
1281
1282
  } catch (error) {
1282
1283
  console.error(chalk.red(error.message));
1283
1284
  console.log(chalk.green(usageMessage));
@@ -1294,10 +1295,10 @@ export async function syncDirectory(args = []) {
1294
1295
  }
1295
1296
 
1296
1297
  // Step 2: Fetch remote directory contents
1297
- const remoteFiles = await listRemoteFiles(remoteDir);
1298
+ let remoteFiles = await listRemoteFiles(remoteDir);
1298
1299
  if (!Array.isArray(remoteFiles)) {
1299
- console.error(chalk.red('Failed to fetch remote directory contents.'));
1300
- return;
1300
+ console.log(chalk.yellow('Remote directory is empty or does not exist. Continuing...'));
1301
+ remoteFiles = [];
1301
1302
  }
1302
1303
 
1303
1304
  // Step 3: List local files
@@ -1311,30 +1312,34 @@ export async function syncDirectory(args = []) {
1311
1312
  // Step 5: Handle conflicts (if any)
1312
1313
  const conflicts = findConflicts(toUpload, toDownload);
1313
1314
  if (conflicts.length > 0) {
1314
-
1315
- console.log(chalk.yellow('The following files have conflicts:'));
1316
- conflicts.forEach(file => console.log(chalk.dim(`- ${file}`)));
1317
-
1318
- const { resolve } = await inquirer.prompt([
1319
- {
1320
- type: 'list',
1321
- name: 'resolve',
1322
- message: 'How would you like to resolve conflicts?',
1323
- choices: [
1324
- { name: 'Keep local version', value: 'local' },
1325
- { name: 'Keep remote version', value: 'remote' },
1326
- { name: 'Skip conflicting files', value: 'skip' }
1327
- ]
1328
- }
1329
- ]);
1330
-
1331
- if (resolve === 'local') {
1315
+ if (overwriteFlag) {
1316
+ console.log(chalk.yellow('Overwriting existing files with local version.'));
1332
1317
  filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1333
- } else if (resolve === 'remote') {
1334
- filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1335
1318
  } else {
1336
- filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1337
- filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1319
+ console.log(chalk.yellow('The following files have conflicts:'));
1320
+ conflicts.forEach(file => console.log(chalk.dim(`- ${file}`)));
1321
+
1322
+ const { resolve } = await inquirer.prompt([
1323
+ {
1324
+ type: 'list',
1325
+ name: 'resolve',
1326
+ message: 'How would you like to resolve conflicts?',
1327
+ choices: [
1328
+ { name: 'Keep local version', value: 'local' },
1329
+ { name: 'Keep remote version', value: 'remote' },
1330
+ { name: 'Skip conflicting files', value: 'skip' }
1331
+ ]
1332
+ }
1333
+ ]);
1334
+
1335
+ if (resolve === 'local') {
1336
+ filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1337
+ } else if (resolve === 'remote') {
1338
+ filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1339
+ } else {
1340
+ filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1341
+ filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1342
+ }
1338
1343
  }
1339
1344
  }
1340
1345
 
@@ -26,7 +26,7 @@ export async function init() {
26
26
  }
27
27
  ]);
28
28
 
29
- let jsFiles = ['puter-sdk'];
29
+ let jsFiles = [];
30
30
  let jsDevFiles = [];
31
31
  let cssFiles = [];
32
32
  let jsExtraLibraries = [];
@@ -2,7 +2,7 @@ import chalk from 'chalk';
2
2
  import fetch from 'node-fetch';
3
3
  import Table from 'cli-table3';
4
4
  import { getCurrentUserName, getCurrentDirectory } from './auth.js';
5
- import { API_BASE, getHeaders, generateAppName, resolvePath, isValidAppName } from '../commons.js';
5
+ import { API_BASE, getHeaders, generateAppName, resolveRemotePath, isValidAppName } from '../commons.js';
6
6
  import { displayNonNullValues, formatDate, isValidAppUuid } from '../utils.js';
7
7
  import { getSubdomains, createSubdomain, deleteSubdomain } from './subdomains.js';
8
8
  import { ErrorAPI } from '../modules/ErrorModule.js';
@@ -98,7 +98,7 @@ export async function infoSite(args = []) {
98
98
  }
99
99
  }
100
100
 
101
- /**
101
+ /**
102
102
  * Delete hosted web site
103
103
  * @param {any[]} args Array of site uuid
104
104
  */
@@ -109,6 +109,10 @@ export async function infoSite(args = []) {
109
109
  }
110
110
  for (const uuid of args)
111
111
  try {
112
+ if (!uuid){
113
+ console.log(chalk.yellow(`We could not find the site ID: ${uuid}`));
114
+ return false;
115
+ }
112
116
  // The uuid must be prefixed with: 'subdomainObj-'
113
117
  const response = await fetch(`${API_BASE}/delete-site`, {
114
118
  headers: getHeaders(),
@@ -136,7 +140,7 @@ export async function infoSite(args = []) {
136
140
  return true;
137
141
  }
138
142
 
139
- /**
143
+ /**
140
144
  * Create a static web app from the current directory to Puter cloud.
141
145
  * @param {string[]} args - Command-line arguments (e.g., [name, --subdomain=<subdomain>]).
142
146
  */
@@ -151,8 +155,10 @@ export async function infoSite(args = []) {
151
155
 
152
156
  const appName = args[0]; // Site name (required)
153
157
  const subdomainOption = args.find(arg => arg.toLocaleLowerCase().startsWith('--subdomain='))?.split('=')[1]; // Optional subdomain
158
+ const remoteDirArg = (args[1] && !args[1].startsWith('--')) ? args[1] : '.';
159
+
154
160
  // Use the current directory as the root directory if none specified
155
- const remoteDir = resolvePath(getCurrentDirectory(), (args[1] && !args[1].startsWith('--'))?args[1]:'.');
161
+ const remoteDir = resolveRemotePath(getCurrentDirectory(), remoteDirArg);
156
162
 
157
163
  console.log(chalk.dim(`Creating site ${chalk.green(appName)} from: ${chalk.green(remoteDir)}...\n`));
158
164
  try {
@@ -212,8 +218,10 @@ export async function infoSite(args = []) {
212
218
 
213
219
  console.log(chalk.green(`App ${chalk.dim(appName)} created successfully and accessible at:`));
214
220
  console.log(chalk.cyan(`https://${site.subdomain}.puter.site`));
221
+ return site;
215
222
  } catch (error) {
216
223
  console.error(chalk.red('Failed to create site.'));
217
224
  console.error(chalk.red(`Error: ${error.message}`));
225
+ return null;
218
226
  }
219
227
  }
package/src/commons.js CHANGED
@@ -155,6 +155,19 @@ export function resolvePath(currentPath, relativePath) {
155
155
  return currentPath;
156
156
  }
157
157
 
158
+ /**
159
+ * Resolve a remote path to an absolute path, handling both absolute and relative paths.
160
+ * @param {string} currentPath - The current working directory.
161
+ * @param {string} remotePath - The remote path to resolve.
162
+ * @returns {string} The resolved absolute path.
163
+ */
164
+ export function resolveRemotePath(currentPath, remotePath) {
165
+ if (remotePath.startsWith('/')) {
166
+ return remotePath;
167
+ }
168
+ return resolvePath(currentPath, remotePath);
169
+ }
170
+
158
171
  /**
159
172
  * Checks if a given string is a valid app name.
160
173
  * The name must:
@@ -207,114 +220,114 @@ export function isValidAppName(name) {
207
220
  */
208
221
  export function getDefaultHomePage(appName, jsFiles = [], cssFiles= []) {
209
222
  const defaultIndexContent = `<!DOCTYPE html>
210
- <html lang="en">
211
- <head>
212
- <meta charset="UTF-8">
213
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
214
- <title>${appName}</title>
215
- ${cssFiles.map(css => `<link href="${css}" rel="stylesheet">`).join('\n ')}
216
- <style>
217
- body {
218
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
219
- line-height: 1.6;
220
- max-width: 800px;
221
- margin: 0 auto;
222
- padding: 20px;
223
- background: #f9fafb;
224
- color: #1f2937;
225
- }
226
- .container {
227
- background: white;
228
- padding: 2rem;
229
- border-radius: 8px;
230
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
231
- }
232
- h1 {
233
- color: #2563eb;
234
- margin-bottom: 1rem;
235
- }
236
- .code-block {
237
- background: #f1f5f9;
238
- padding: 1rem;
239
- border-radius: 4px;
240
- font-family: monospace;
241
- overflow-x: auto;
242
- }
243
- .tip {
244
- background: #dbeafe;
245
- border-left: 4px solid #2563eb;
246
- padding: 1rem;
247
- margin: 1rem 0;
248
- }
249
- .links {
250
- display: flex;
251
- gap: 1rem;
252
- margin-top: 2rem;
253
- }
254
- .links a {
255
- color: #2563eb;
256
- text-decoration: none;
257
- }
258
- .links a:hover {
259
- text-decoration: underline;
260
- }
261
- .footer {
262
- text-align: center;
263
- margin-top: 50px;
264
- color: var(--color-grey);
265
- font-size: 0.9rem;
266
- }
267
- </style>
268
- </head>
269
- <body>
270
- <div class="container">
271
- <h1>🚀 Welcome to ${appName}!</h1>
272
-
273
- <p>This is your new website powered by Puter. You can start customizing it right away!</p>
274
-
275
- <div class="tip">
276
- <strong>Quick Tip:</strong> Replace this content with your own by editing the <code>index.html</code> file.
277
- </div>
278
-
279
- <h2>🌟 Getting Started</h2>
280
-
281
- <p>Here's a simple example using Puter.js:</p>
282
-
283
- <div class="code-block">
284
- &lt;script src="https://js.puter.com/v2/">&lt;/script>
285
- &lt;script>
286
- // Create a new file in the cloud
287
- puter.fs.write('hello.txt', 'Hello, Puter!')
288
- .then(file => console.log(\`File created at: \${file.path}\`));
289
- &lt;/script>
290
- </div>
291
-
292
- <h2>💡 Key Features</h2>
293
- <ul>
294
- <li>Cloud Storage</li>
295
- <li>AI Services (GPT-4, DALL-E)</li>
296
- <li>Static Website Hosting</li>
297
- <li>Key-Value Store</li>
298
- <li>Authentication</li>
299
- </ul>
300
-
301
- <div class="links">
302
- <a href="https://docs.puter.com" target="_blank">📚 Documentation</a>
303
- <a href="https://discord.gg/puter" target="_blank">💬 Discord Community</a>
304
- <a href="https://github.com/HeyPuter" target="_blank">👩‍💻 GitHub</a>
305
- </div>
223
+ <html lang="en">
224
+ <head>
225
+ <meta charset="UTF-8">
226
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
227
+ <title>${appName}</title>
228
+ ${cssFiles.map(css => `<link href="${css}" rel="stylesheet">`).join('\n ')}
229
+ <style>
230
+ body {
231
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
232
+ line-height: 1.6;
233
+ max-width: 800px;
234
+ margin: 0 auto;
235
+ padding: 20px;
236
+ background: #f9fafb;
237
+ color: #1f2937;
238
+ }
239
+ .container {
240
+ background: white;
241
+ padding: 2rem;
242
+ border-radius: 8px;
243
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
244
+ }
245
+ h1 {
246
+ color: #2563eb;
247
+ margin-bottom: 1rem;
248
+ }
249
+ .code-block {
250
+ background: #f1f5f9;
251
+ padding: 1rem;
252
+ border-radius: 4px;
253
+ font-family: monospace;
254
+ overflow-x: auto;
255
+ }
256
+ .tip {
257
+ background: #dbeafe;
258
+ border-left: 4px solid #2563eb;
259
+ padding: 1rem;
260
+ margin: 1rem 0;
261
+ }
262
+ .links {
263
+ display: flex;
264
+ gap: 1rem;
265
+ margin-top: 2rem;
266
+ }
267
+ .links a {
268
+ color: #2563eb;
269
+ text-decoration: none;
270
+ }
271
+ .links a:hover {
272
+ text-decoration: underline;
273
+ }
274
+ .footer {
275
+ text-align: center;
276
+ margin-top: 50px;
277
+ color: var(--color-grey);
278
+ font-size: 0.9rem;
279
+ }
280
+ </style>
281
+ </head>
282
+ <body>
283
+ <div class="container">
284
+ <h1>🚀 Welcome to ${appName}!</h1>
285
+
286
+ <p>This is your new website powered by Puter. You can start customizing it right away!</p>
287
+
288
+ <div class="tip">
289
+ <strong>Quick Tip:</strong> Replace this content with your own by editing the <code>index.html</code> file.
306
290
  </div>
307
291
 
308
- <footer class="footer">
309
- &copy; 2025 ${appName}. All rights reserved.
310
- </footer>
292
+ <h2>🌟 Getting Started</h2>
293
+
294
+ <p>Here's a simple example using Puter.js:</p>
295
+
296
+ <div class="code-block">
297
+ &lt;script src="https://js.puter.com/v2/">&lt;/script>
298
+ &lt;script>
299
+ // Create a new file in the cloud
300
+ puter.fs.write('hello.txt', 'Hello, Puter!')
301
+ .then(file => console.log(\`File created at: \${file.path}\`));
302
+ &lt;/script>
303
+ </div>
311
304
 
312
- <div id="${(jsFiles.length && jsFiles.some(f => f.includes('react'))) ? 'root' : 'app'}"></div>
313
- ${jsFiles.map(js =>
314
- `<script ${js.endsWith('app.js') ? 'type="text/babel"' : ''} src="${js}"></script>`
315
- ).join('\n ')}
316
- </body>
317
- </html>`;
305
+ <h2>💡 Key Features</h2>
306
+ <ul>
307
+ <li>Cloud Storage</li>
308
+ <li>AI Services (GPT-4, DALL-E)</li>
309
+ <li>Static Website Hosting</li>
310
+ <li>Key-Value Store</li>
311
+ <li>Authentication</li>
312
+ </ul>
313
+
314
+ <div class="links">
315
+ <a href="https://docs.puter.com" target="_blank">📚 Documentation</a>
316
+ <a href="https://discord.gg/puter" target="_blank">💬 Discord Community</a>
317
+ <a href="https://github.com/HeyPuter" target="_blank">👩‍💻 GitHub</a>
318
+ </div>
319
+ </div>
320
+
321
+ <footer class="footer">
322
+ &copy; 2025 ${appName}. All rights reserved.
323
+ </footer>
324
+
325
+ <div id="${(jsFiles.length && jsFiles.some(f => f.includes('react'))) ? 'root' : 'app'}"></div>
326
+ ${jsFiles.map(js =>
327
+ `<script ${js.endsWith('app.js') ? 'type="text/babel"' : ''} src="${js}"></script>`
328
+ ).join('\n ')}
329
+ </body>
330
+ </html>`;
318
331
 
319
332
  return defaultIndexContent;
320
333
  }
package/src/executor.js CHANGED
@@ -7,6 +7,7 @@ import { listFiles, makeDirectory, renameFileOrDirectory,
7
7
  getInfo, getDiskUsage, createFile, readFile, uploadFile,
8
8
  downloadFile, copyFile, syncDirectory, editFile } from './commands/files.js';
9
9
  import { getUserInfo, getUsageInfo, login } from './commands/auth.js';
10
+ import { deploy } from './commands/deploy.js';
10
11
  import { PROJECT_NAME, API_BASE, getHeaders } from './commons.js';
11
12
  import inquirer from 'inquirer';
12
13
  import { exec } from 'node:child_process';
@@ -147,6 +148,7 @@ const commands = {
147
148
  site: infoSite,
148
149
  'site:delete': deleteSite,
149
150
  'site:create': createSite,
151
+ 'site:deploy': deploy,
150
152
  };
151
153
 
152
154
  /**
@@ -352,6 +354,11 @@ function showHelp(command) {
352
354
  Create a static website from directory.
353
355
  Example: site:create mywebsite /path/to/dir --subdomain=mywebsite
354
356
  `,
357
+ 'site:deploy': `
358
+ ${chalk.cyan('site:deploy [<valid_name_app>] [<remote_dir>] [--subdomain=<subdomain>]')}
359
+ Deploy a local web project to Puter.
360
+ Example: site:deploy my-app ./my-app --subdomain my-app
361
+ `,
355
362
  '!': `
356
363
  ${chalk.cyan('!<command>')}
357
364
  Execute a command on the host machine.
@@ -116,7 +116,7 @@ class ProfileModule {
116
116
  return this.addProfileWizard();
117
117
  }
118
118
 
119
- console.log('doing this branch');
119
+ // console.log('doing this branch');
120
120
  const answer = await inquirer.prompt([
121
121
  {
122
122
  name: 'profile',
@@ -183,7 +183,7 @@ class ProfileModule {
183
183
 
184
184
 
185
185
  const contentType = response.headers.get('content-type');
186
- console.log('content type?', '|' + contentType + '|');
186
+ //console.log('content type?', '|' + contentType + '|');
187
187
 
188
188
  // TODO: proper content type parsing
189
189
  if ( ! contentType.trim().startsWith('application/json') ) {
@@ -241,7 +241,7 @@ class ProfileModule {
241
241
  if (args.save) {
242
242
  const localEnvFile = '.env';
243
243
  try {
244
- // Check if the file exists, if so then delete it before writing.
244
+ // Check if the file exists, if so then append the api key to the EOF.
245
245
  if (fs.existsSync(localEnvFile)) {
246
246
  console.log(chalk.yellow(`File "${localEnvFile}" already exists... Adding token.`));
247
247
  fs.appendFileSync(localEnvFile, `\nPUTER_API_KEY="${data.token}"`, 'utf8');