@launchframe/cli 1.0.0 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchframe/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Production-ready B2B SaaS boilerplate with subscriptions, credits, and multi-tenancy",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -3,24 +3,29 @@ const { clearCache, getCacheInfo } = require('../utils/service-cache');
3
3
 
4
4
  /**
5
5
  * Clear service cache
6
+ * @param {Object} flags - Optional flags
7
+ * @param {boolean} flags.yes - Skip confirmation prompt
8
+ * @param {boolean} flags.y - Skip confirmation prompt (short form)
6
9
  */
7
- async function cacheClear() {
10
+ async function cacheClear(flags = {}) {
8
11
  console.log(chalk.yellow('\n⚠️ This will delete all cached services'));
9
12
  console.log(chalk.gray('You will need to re-download on next init or service:add\n'));
10
-
11
- const inquirer = require('inquirer');
12
- const { confirmed } = await inquirer.prompt([{
13
- type: 'confirm',
14
- name: 'confirmed',
15
- message: 'Continue with cache clear?',
16
- default: false
17
- }]);
18
-
19
- if (!confirmed) {
20
- console.log('Cancelled');
21
- return;
13
+
14
+ if (!(flags.yes || flags.y)) {
15
+ const inquirer = require('inquirer');
16
+ const { confirmed } = await inquirer.prompt([{
17
+ type: 'confirm',
18
+ name: 'confirmed',
19
+ message: 'Continue with cache clear?',
20
+ default: false
21
+ }]);
22
+
23
+ if (!confirmed) {
24
+ console.log('Cancelled');
25
+ return;
26
+ }
22
27
  }
23
-
28
+
24
29
  await clearCache();
25
30
  }
26
31
 
@@ -5,7 +5,7 @@ const inquirer = require('inquirer');
5
5
  const { spawnSync } = require('child_process');
6
6
  const { requireProject, getProjectConfig } = require('../utils/project-helpers');
7
7
 
8
- async function databaseConsole({ remote = false } = {}) {
8
+ async function databaseConsole({ remote = false, query = null, skipPermission = false } = {}) {
9
9
  requireProject();
10
10
 
11
11
  const infrastructurePath = path.join(process.cwd(), 'infrastructure');
@@ -28,23 +28,45 @@ async function databaseConsole({ remote = false } = {}) {
28
28
 
29
29
  const { vpsUser, vpsHost, vpsAppFolder } = config.deployment;
30
30
 
31
- // 2. Warn before connecting to production
32
- console.log(chalk.yellow.bold('\n⚠️ You are about to connect to the PRODUCTION database.\n'));
33
- console.log(chalk.gray(` Host: ${vpsHost}`));
34
- console.log(chalk.gray(` Folder: ${vpsAppFolder}\n`));
35
-
36
- const { confirmed } = await inquirer.prompt([
37
- {
38
- type: 'confirm',
39
- name: 'confirmed',
40
- message: 'Are you sure you want to open a console to the production database?',
41
- default: false
31
+ if (query) {
32
+ // Non-interactive query mode pipe SQL via stdin, skip confirmation
33
+ const remoteCmd = `cd ${vpsAppFolder}/infrastructure && docker compose -f docker-compose.yml -f docker-compose.prod.yml exec -T database sh -c 'psql -U $POSTGRES_USER $POSTGRES_DB'`;
34
+ const result = spawnSync('ssh', ['-T', `${vpsUser}@${vpsHost}`, remoteCmd], {
35
+ input: query,
36
+ stdio: ['pipe', 'pipe', 'pipe'],
37
+ encoding: 'utf8'
38
+ });
39
+
40
+ if (result.stdout) process.stdout.write(result.stdout);
41
+ if (result.stderr) process.stderr.write(result.stderr);
42
+
43
+ if (result.status !== 0) {
44
+ console.error(chalk.red('\n❌ Could not execute query on the production database.'));
45
+ console.log(chalk.gray('Check that the VPS is reachable and services are running.\n'));
46
+ process.exit(1);
42
47
  }
43
- ]);
48
+ return;
49
+ }
44
50
 
45
- if (!confirmed) {
46
- console.log(chalk.gray('\nAborted.\n'));
47
- process.exit(0);
51
+ // 2. Warn before connecting to production (interactive mode only)
52
+ if (!skipPermission) {
53
+ console.log(chalk.yellow.bold('\n⚠️ You are about to connect to the PRODUCTION database.\n'));
54
+ console.log(chalk.gray(` Host: ${vpsHost}`));
55
+ console.log(chalk.gray(` Folder: ${vpsAppFolder}\n`));
56
+
57
+ const { confirmed } = await inquirer.prompt([
58
+ {
59
+ type: 'confirm',
60
+ name: 'confirmed',
61
+ message: 'Are you sure you want to open a console to the production database?',
62
+ default: false
63
+ }
64
+ ]);
65
+
66
+ if (!confirmed) {
67
+ console.log(chalk.gray('\nAborted.\n'));
68
+ process.exit(0);
69
+ }
48
70
  }
49
71
 
50
72
  console.log(chalk.blue.bold('\n🔌 Connecting to production database...\n'));
@@ -62,6 +84,32 @@ async function databaseConsole({ remote = false } = {}) {
62
84
  process.exit(1);
63
85
  }
64
86
  } else {
87
+ if (query) {
88
+ // Non-interactive local query mode — pipe SQL via stdin
89
+ const psqlCmd = [
90
+ 'compose', '-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml',
91
+ 'exec', '-T', 'database', 'sh', '-c', 'psql -U $POSTGRES_USER $POSTGRES_DB'
92
+ ];
93
+
94
+ const result = spawnSync('docker', psqlCmd, {
95
+ cwd: infrastructurePath,
96
+ input: query,
97
+ stdio: ['pipe', 'pipe', 'pipe'],
98
+ encoding: 'utf8'
99
+ });
100
+
101
+ if (result.stdout) process.stdout.write(result.stdout);
102
+ if (result.stderr) process.stderr.write(result.stderr);
103
+
104
+ if (result.status !== 0) {
105
+ console.error(chalk.red('\n❌ Could not execute query on the local database container.'));
106
+ console.log(chalk.gray('Make sure services are running:'));
107
+ console.log(chalk.white(' launchframe docker:up\n'));
108
+ process.exit(1);
109
+ }
110
+ return;
111
+ }
112
+
65
113
  console.log(chalk.blue.bold('\n🗄️ Opening local database console...\n'));
66
114
 
67
115
  // Let the shell inside the container expand $POSTGRES_USER / $POSTGRES_DB
@@ -34,7 +34,7 @@ function sqlBool(val) {
34
34
  return val ? 'true' : 'false';
35
35
  }
36
36
 
37
- async function deploySyncFeatures() {
37
+ async function deploySyncFeatures(flags = {}) {
38
38
  requireProject();
39
39
 
40
40
  // Step 1 — Project + infrastructure check
@@ -162,18 +162,20 @@ async function deploySyncFeatures() {
162
162
  console.log(chalk.gray(` Remote host: ${vpsHost}\n`));
163
163
  console.log(chalk.red('This will TRUNCATE subscription_plan_features (cascades to feature values).\n'));
164
164
 
165
- const { confirmed } = await inquirer.prompt([
166
- {
167
- type: 'confirm',
168
- name: 'confirmed',
169
- message: 'Are you sure you want to sync features to production?',
170
- default: false
165
+ if (!flags.yes) {
166
+ const { confirmed } = await inquirer.prompt([
167
+ {
168
+ type: 'confirm',
169
+ name: 'confirmed',
170
+ message: 'Are you sure you want to sync features to production?',
171
+ default: false
172
+ }
173
+ ]);
174
+
175
+ if (!confirmed) {
176
+ console.log(chalk.gray('\nAborted.\n'));
177
+ process.exit(0);
171
178
  }
172
- ]);
173
-
174
- if (!confirmed) {
175
- console.log(chalk.gray('\nAborted.\n'));
176
- process.exit(0);
177
179
  }
178
180
 
179
181
  // Step 9 — Build sync SQL transaction
@@ -30,12 +30,13 @@ function writeFile(filePath, data) {
30
30
 
31
31
  /**
32
32
  * Generate and inject logo/favicon assets across all relevant frontend services
33
+ * @param {string} svgPath - Optional path to SVG file (defaults to <projectRoot>/logo.svg)
33
34
  */
34
- async function devLogo() {
35
+ async function devLogo(svgPath) {
35
36
  requireProject();
36
37
 
37
38
  const cwd = process.cwd();
38
- const logoPath = path.join(cwd, 'logo.svg');
39
+ const logoPath = svgPath || path.join(cwd, 'logo.svg');
39
40
 
40
41
  if (!fs.existsSync(logoPath)) {
41
42
  console.error(chalk.red('\n❌ Error: logo.svg not found'));
@@ -1,13 +1,17 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { spawn } = require('child_process');
4
+ const { spawn, execSync } = require('child_process');
5
5
  const { requireProject } = require('../utils/project-helpers');
6
6
 
7
7
  /**
8
8
  * View logs from Docker services
9
+ * @param {string} service - Optional service name to filter logs
10
+ * @param {Object} flags - Optional flags
11
+ * @param {boolean} flags['no-follow'] - Snapshot mode: print lines and exit (non-interactive)
12
+ * @param {number} flags.tail - Number of lines to show (default 100, only used with --no-follow)
9
13
  */
10
- async function dockerLogs() {
14
+ async function dockerLogs(service, flags = {}) {
11
15
  requireProject();
12
16
 
13
17
  const infrastructurePath = path.join(process.cwd(), 'infrastructure');
@@ -18,9 +22,24 @@ async function dockerLogs() {
18
22
  process.exit(1);
19
23
  }
20
24
 
21
- // Get optional service name from args (e.g., launchframe docker:logs backend)
22
- const service = process.argv[3];
25
+ const noFollow = flags['no-follow'];
23
26
 
27
+ if (noFollow) {
28
+ // Snapshot mode — print tail and exit (non-interactive, suitable for MCP)
29
+ const tail = flags.tail || 100;
30
+ const args = ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'logs', '--no-follow', '--tail', String(tail)];
31
+ if (service) args.push(service);
32
+
33
+ try {
34
+ execSync(`docker-compose ${args.join(' ')}`, { cwd: infrastructurePath, stdio: 'inherit' });
35
+ } catch (error) {
36
+ console.error(chalk.red('\n❌ Error viewing logs:'), error.message);
37
+ process.exit(1);
38
+ }
39
+ return;
40
+ }
41
+
42
+ // Streaming mode (interactive)
24
43
  console.log(chalk.blue.bold('\n📋 Docker Service Logs\n'));
25
44
 
26
45
  if (service) {
@@ -34,14 +53,14 @@ async function dockerLogs() {
34
53
 
35
54
  try {
36
55
  const logsCommand = 'docker-compose';
37
- const args = ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'logs', '-f'];
56
+ const spawnArgs = ['-f', 'docker-compose.yml', '-f', 'docker-compose.dev.yml', 'logs', '-f'];
38
57
 
39
58
  if (service) {
40
- args.push(service);
59
+ spawnArgs.push(service);
41
60
  }
42
61
 
43
62
  // Use spawn to stream output in real-time
44
- const child = spawn(logsCommand, args, {
63
+ const child = spawn(logsCommand, spawnArgs, {
45
64
  cwd: infrastructurePath,
46
65
  stdio: 'inherit',
47
66
  shell: true
@@ -7,8 +7,10 @@ const { requireProject, getProjectConfig } = require('../utils/project-helpers')
7
7
  /**
8
8
  * Start Docker services (all or specific service)
9
9
  * @param {string} serviceName - Optional service name to start (e.g., 'docs', 'backend')
10
+ * @param {Object} flags - Optional flags
11
+ * @param {boolean} flags.detach - Run detached (docker-compose up -d) instead of watch mode
10
12
  */
11
- async function dockerUp(serviceName) {
13
+ async function dockerUp(serviceName, flags = {}) {
12
14
  requireProject();
13
15
 
14
16
  const infrastructurePath = path.join(process.cwd(), 'infrastructure');
@@ -19,14 +21,26 @@ async function dockerUp(serviceName) {
19
21
  process.exit(1);
20
22
  }
21
23
 
24
+ if (flags.detach) {
25
+ // Detached mode — start services in background (no watch, no blocking)
26
+ const upCommand = serviceName
27
+ ? `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ${serviceName}`
28
+ : 'docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d';
29
+
30
+ console.log(chalk.gray(`Running: ${upCommand}\n`));
31
+ execSync(upCommand, { cwd: infrastructurePath, stdio: 'inherit' });
32
+ console.log(chalk.green.bold('\n✅ Services started in detached mode.\n'));
33
+ return;
34
+ }
35
+
22
36
  // Check Docker Compose version for watch support
23
37
  try {
24
38
  const composeVersion = execSync('docker compose version', { encoding: 'utf8' });
25
39
  const versionMatch = composeVersion.match(/v?(\d+)\.(\d+)\.(\d+)/);
26
-
40
+
27
41
  if (versionMatch) {
28
42
  const [, major, minor] = versionMatch.map(Number);
29
-
43
+
30
44
  if (major < 2 || (major === 2 && minor < 22)) {
31
45
  console.error(chalk.red('\n❌ Error: Docker Compose v2.22+ is required for watch support'));
32
46
  console.log(chalk.yellow(`Current version: Docker Compose v${major}.${minor}`));
@@ -4,7 +4,7 @@ const chalk = require('chalk');
4
4
  const { execSync } = require('child_process');
5
5
  const { requireProject } = require('../utils/project-helpers');
6
6
 
7
- async function migrateCreate() {
7
+ async function migrateCreate(migrationName) {
8
8
  requireProject();
9
9
 
10
10
  const infrastructurePath = path.join(process.cwd(), 'infrastructure');
@@ -15,8 +15,6 @@ async function migrateCreate() {
15
15
  process.exit(1);
16
16
  }
17
17
 
18
- const migrationName = process.argv[3];
19
-
20
18
  if (!migrationName) {
21
19
  console.error(chalk.red('\n❌ Error: migration name is required'));
22
20
  console.log(chalk.gray('Usage: launchframe migrate:create <name>\n'));
@@ -41,7 +41,7 @@ async function moduleList() {
41
41
  console.log(' launchframe module:add <module-name>');
42
42
  }
43
43
 
44
- async function moduleAdd(moduleName) {
44
+ async function moduleAdd(moduleName, flags = {}) {
45
45
  requireProject();
46
46
 
47
47
  // Validate module exists in registry
@@ -97,16 +97,18 @@ async function moduleAdd(moduleName) {
97
97
  console.log(mod.description);
98
98
  console.log(`Affects services: ${mod.services.join(', ')}`);
99
99
 
100
- const { confirmed } = await inquirer.prompt([{
101
- type: 'confirm',
102
- name: 'confirmed',
103
- message: `Add module "${mod.displayName}" to your project?`,
104
- default: true
105
- }]);
106
-
107
- if (!confirmed) {
108
- console.log('Installation cancelled');
109
- process.exit(0);
100
+ if (!(flags.yes || flags.y)) {
101
+ const { confirmed } = await inquirer.prompt([{
102
+ type: 'confirm',
103
+ name: 'confirmed',
104
+ message: `Add module "${mod.displayName}" to your project?`,
105
+ default: true
106
+ }]);
107
+
108
+ if (!confirmed) {
109
+ console.log('Installation cancelled');
110
+ process.exit(0);
111
+ }
110
112
  }
111
113
 
112
114
  const affectedServices = [...new Set(Object.keys(MODULE_CONFIG[moduleName] || {}))].filter(s => s !== 'infrastructure');
@@ -139,8 +141,8 @@ async function moduleAdd(moduleName) {
139
141
  await dockerBuild(service);
140
142
  }
141
143
 
142
- // Restart the full stack in watch mode
143
- await dockerUp();
144
+ // Restart the full stack (detached when running non-interactively, watch otherwise)
145
+ await dockerUp(undefined, flags.yes || flags.y ? { detach: true } : {});
144
146
  }
145
147
 
146
148
  module.exports = { moduleAdd, moduleList };
@@ -10,7 +10,7 @@ const { updateEnvFile } = require('../utils/env-generator');
10
10
  const { checkGitHubAccess, showAccessDeniedMessage } = require('../utils/github-access');
11
11
  const { ensureCacheReady, getServicePath } = require('../utils/service-cache');
12
12
 
13
- async function serviceAdd(serviceName) {
13
+ async function serviceAdd(serviceName, flags = {}) {
14
14
  // STEP 1: Validation
15
15
  console.log(chalk.blue(`Installing ${serviceName} service...`));
16
16
 
@@ -49,16 +49,18 @@ async function serviceAdd(serviceName) {
49
49
  console.log(`Tech stack: ${service.techStack}`);
50
50
  console.log(`Dependencies: ${service.dependencies.join(', ')}`);
51
51
 
52
- const { confirmed } = await inquirer.prompt([{
53
- type: 'confirm',
54
- name: 'confirmed',
55
- message: 'Continue with installation?',
56
- default: true
57
- }]);
52
+ if (!(flags.yes || flags.y)) {
53
+ const { confirmed } = await inquirer.prompt([{
54
+ type: 'confirm',
55
+ name: 'confirmed',
56
+ message: 'Continue with installation?',
57
+ default: true
58
+ }]);
58
59
 
59
- if (!confirmed) {
60
- console.log('Installation cancelled');
61
- process.exit(0);
60
+ if (!confirmed) {
61
+ console.log('Installation cancelled');
62
+ process.exit(0);
63
+ }
62
64
  }
63
65
 
64
66
  // STEP 3: Get service files (from cache in production, local in dev)
@@ -124,7 +126,7 @@ async function serviceAdd(serviceName) {
124
126
 
125
127
  // STEP 4: Service-specific prompts (e.g., Airtable credentials)
126
128
  console.log(chalk.blue('\nConfiguring service...'));
127
- const envValues = await runServicePrompts(service);
129
+ const envValues = await runServicePrompts(service, flags);
128
130
 
129
131
  // STEP 5: Replace template variables
130
132
  console.log(chalk.blue('\nCustomizing service for your project...'));
@@ -265,9 +267,17 @@ async function serviceAdd(serviceName) {
265
267
  console.log(`\n📖 See README.md in ${serviceName}/ for more details.`);
266
268
  }
267
269
 
268
- async function runServicePrompts(service) {
270
+ async function runServicePrompts(service, flags = {}) {
269
271
  const envValues = {};
270
272
 
273
+ if (flags.yes || flags.y) {
274
+ // Non-interactive mode — use empty values; configure manually after install
275
+ for (const key of Object.keys(service.envVars)) {
276
+ envValues[key] = '';
277
+ }
278
+ return envValues;
279
+ }
280
+
271
281
  // Prompt for each required env var
272
282
  for (const [key, description] of Object.entries(service.envVars)) {
273
283
  const { value } = await inquirer.prompt([{
@@ -1,11 +1,14 @@
1
1
  const chalk = require('chalk');
2
- const { spawn } = require('child_process');
2
+ const { spawn, spawnSync } = require('child_process');
3
3
  const { requireProject, getProjectConfig, isWaitlistInstalled } = require('../utils/project-helpers');
4
4
 
5
5
  /**
6
- * View waitlist logs from VPS (streaming)
6
+ * View waitlist logs from VPS
7
+ * @param {Object} flags - Optional flags
8
+ * @param {boolean} flags['no-follow'] - Snapshot mode: print tail and exit (non-interactive)
9
+ * @param {number} flags.tail - Number of lines to show (default 100, used with --no-follow)
7
10
  */
8
- async function waitlistLogs() {
11
+ async function waitlistLogs(flags = {}) {
9
12
  requireProject();
10
13
 
11
14
  console.log(chalk.blue.bold('\n📋 Waitlist Logs\n'));
@@ -28,6 +31,20 @@ async function waitlistLogs() {
28
31
 
29
32
  const { vpsHost, vpsUser, vpsAppFolder } = config.deployment;
30
33
 
34
+ if (flags['no-follow']) {
35
+ // Snapshot mode — print tail and exit (non-interactive, suitable for MCP)
36
+ const tail = flags.tail || 100;
37
+ const sshCmd = `cd ${vpsAppFolder}/waitlist && docker-compose -f docker-compose.waitlist.yml logs --no-follow --tail=${tail}`;
38
+
39
+ const result = spawnSync('ssh', [`${vpsUser}@${vpsHost}`, sshCmd], { stdio: 'inherit' });
40
+
41
+ if (result.status !== 0 && result.status !== null) {
42
+ console.log(chalk.yellow(`\n⚠️ Process exited with code ${result.status}\n`));
43
+ }
44
+ return;
45
+ }
46
+
47
+ // Streaming mode (interactive)
31
48
  console.log(chalk.gray('Connecting to VPS and streaming logs...\n'));
32
49
  console.log(chalk.gray('Press Ctrl+C to exit\n'));
33
50
 
package/src/index.js CHANGED
@@ -63,14 +63,22 @@ function parseFlags(args) {
63
63
  for (let i = 1; i < args.length; i++) {
64
64
  const arg = args[i];
65
65
  if (arg.startsWith('--')) {
66
- const flagName = arg.substring(2);
67
- const nextArg = args[i + 1];
68
- // Check if next arg is a value (not a flag)
69
- if (nextArg && !nextArg.startsWith('-')) {
70
- flags[flagName] = nextArg;
71
- i++; // Skip next arg since we consumed it
66
+ const withoutDashes = arg.substring(2);
67
+ const eqIndex = withoutDashes.indexOf('=');
68
+ if (eqIndex !== -1) {
69
+ // --key=value format
70
+ const flagName = withoutDashes.substring(0, eqIndex);
71
+ flags[flagName] = withoutDashes.substring(eqIndex + 1) || true;
72
72
  } else {
73
- flags[flagName] = true; // Boolean flag
73
+ const flagName = withoutDashes;
74
+ const nextArg = args[i + 1];
75
+ // Check if next arg is a value (not a flag)
76
+ if (nextArg && !nextArg.startsWith('-')) {
77
+ flags[flagName] = nextArg;
78
+ i++; // Skip next arg since we consumed it
79
+ } else {
80
+ flags[flagName] = true; // Boolean flag
81
+ }
74
82
  }
75
83
  } else if (arg.startsWith('-') && arg.length === 2) {
76
84
  const flagName = arg.substring(1);
@@ -132,7 +140,7 @@ async function main() {
132
140
  await deployBuild(args[1]); // Optional service name
133
141
  break;
134
142
  case 'deploy:sync-features':
135
- await deploySyncFeatures();
143
+ await deploySyncFeatures(flags);
136
144
  break;
137
145
  case 'waitlist:deploy':
138
146
  await waitlistDeploy();
@@ -144,19 +152,19 @@ async function main() {
144
152
  await waitlistDown();
145
153
  break;
146
154
  case 'waitlist:logs':
147
- await waitlistLogs();
155
+ await waitlistLogs(flags);
148
156
  break;
149
157
  case 'docker:build':
150
158
  await dockerBuild(args[1]); // Optional service name
151
159
  break;
152
160
  case 'docker:up':
153
- await dockerUp(args[1]); // Pass optional service name
161
+ await dockerUp(args[1] || flags.service, flags);
154
162
  break;
155
163
  case 'docker:down':
156
164
  await dockerDown();
157
165
  break;
158
166
  case 'docker:logs':
159
- await dockerLogs();
167
+ await dockerLogs(args[1] || flags.service, flags);
160
168
  break;
161
169
  case 'docker:destroy':
162
170
  await dockerDestroy({ force: flags.force || flags.f });
@@ -165,24 +173,24 @@ async function main() {
165
173
  await migrateRun();
166
174
  break;
167
175
  case 'migration:create':
168
- await migrateCreate();
176
+ await migrateCreate(args[1] || flags.name);
169
177
  break;
170
178
  case 'migration:revert':
171
179
  await migrateRevert();
172
180
  break;
173
181
  case 'database:console':
174
- await databaseConsole({ remote: flags.remote });
182
+ await databaseConsole({ remote: flags.remote, query: flags.query, skipPermission: !!flags['skip-permission'] });
175
183
  break;
176
184
  case 'doctor':
177
185
  await doctor();
178
186
  break;
179
187
  case 'service:add':
180
- if (!args[1]) {
188
+ if (!args[1] && !flags.service) {
181
189
  console.error(chalk.red('Error: Service name required'));
182
190
  console.log('Usage: launchframe service:add <service-name>');
183
191
  process.exit(1);
184
192
  }
185
- await serviceAdd(args[1]);
193
+ await serviceAdd(args[1] || flags.service, flags);
186
194
  break;
187
195
  case 'service:list':
188
196
  await serviceList();
@@ -196,18 +204,18 @@ async function main() {
196
204
  await serviceRemove(args[1]);
197
205
  break;
198
206
  case 'module:add':
199
- if (!args[1]) {
207
+ if (!args[1] && !flags.name) {
200
208
  console.error(chalk.red('Error: Module name required'));
201
209
  console.log('Usage: launchframe module:add <module-name>');
202
210
  process.exit(1);
203
211
  }
204
- await moduleAdd(args[1]);
212
+ await moduleAdd(args[1] || flags.name, flags);
205
213
  break;
206
214
  case 'module:list':
207
215
  await moduleList();
208
216
  break;
209
217
  case 'cache:clear':
210
- await cacheClear();
218
+ await cacheClear(flags);
211
219
  break;
212
220
  case 'cache:info':
213
221
  await cacheInfo();
@@ -222,7 +230,7 @@ async function main() {
222
230
  await devQueue();
223
231
  break;
224
232
  case 'dev:logo':
225
- await devLogo();
233
+ await devLogo(args[1] || flags.svg);
226
234
  break;
227
235
  case 'dev:npm-install':
228
236
  if (!args[1]) {