olly-molly 0.3.4 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +83 -0
  2. package/bin/cli.js +434 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,7 @@
10
10
 
11
11
  <p align="center">
12
12
  <a href="#quick-start">Quick Start</a> •
13
+ <a href="#cli-options">CLI Options</a> •
13
14
  <a href="#features">Features</a> •
14
15
  <a href="#how-it-works">How It Works</a> •
15
16
  <a href="#contributing">Contributing</a>
@@ -133,6 +134,88 @@ olly-molly-darwin-x64.tar.gz
133
134
  olly-molly-win32-x64.tar.gz
134
135
  ```
135
136
 
137
+ ### CLI Options
138
+
139
+ ```bash
140
+ olly-molly [options]
141
+ ```
142
+
143
+ #### Server Settings
144
+
145
+ | Flag | Short | Default | Description |
146
+ |------|-------|---------|-------------|
147
+ | `--port` | `-p` | `1234` | Server port |
148
+ | `--host` | `-H` | `localhost` | Binding host |
149
+ | `--no-open` | | | Disable auto browser open |
150
+
151
+ #### Data/Path Settings
152
+
153
+ | Flag | Short | Default | Description |
154
+ |------|-------|---------|-------------|
155
+ | `--data-dir` | `-d` | `~/.olly-molly` | App data directory |
156
+ | `--db-path` | | `<data-dir>/db` | Database path |
157
+
158
+ #### Development
159
+
160
+ | Flag | Short | Description |
161
+ |------|-------|-------------|
162
+ | `--dev` | | Run in development mode (`next dev`) |
163
+ | `--verbose` | `-v` | Enable verbose logging |
164
+
165
+ #### Advanced Options
166
+
167
+ | Flag | Description |
168
+ |------|-------------|
169
+ | `--reset` | Reset all app data (with confirmation prompt) |
170
+ | `--export-db <path>` | Export database to tar.gz file |
171
+ | `--import-db <path>` | Import database from tar.gz file |
172
+
173
+ #### Info
174
+
175
+ | Flag | Short | Description |
176
+ |------|-------|-------------|
177
+ | `--version` | `-V` | Show version and exit |
178
+ | `--help` | `-h` | Show help and exit |
179
+
180
+ #### Environment Variables
181
+
182
+ You can also configure Olly Molly using environment variables (CLI arguments take priority):
183
+
184
+ | Variable | Description |
185
+ |----------|-------------|
186
+ | `OLLY_MOLLY_PORT` | Server port |
187
+ | `OLLY_MOLLY_HOST` | Binding host |
188
+ | `OLLY_MOLLY_DATA_DIR` | App data directory |
189
+ | `OLLY_MOLLY_DB_PATH` | Database path |
190
+
191
+ #### Examples
192
+
193
+ ```bash
194
+ # Start with defaults (port 1234, auto-open browser)
195
+ npx olly-molly
196
+
197
+ # Use custom port
198
+ npx olly-molly -p 3000
199
+
200
+ # Bind to all interfaces (for network access)
201
+ npx olly-molly --host 0.0.0.0
202
+
203
+ # Run in development mode with verbose logging
204
+ npx olly-molly --dev -v
205
+
206
+ # Disable auto browser open
207
+ npx olly-molly --no-open
208
+
209
+ # Export database for backup
210
+ npx olly-molly --export-db backup.tar.gz
211
+
212
+ # Import database from backup
213
+ npx olly-molly --import-db backup.tar.gz
214
+
215
+ # Reset all app data
216
+ npx olly-molly --reset
217
+ ```
218
+
136
219
  ### Or install globally
137
220
 
138
221
  ```bash
package/bin/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { spawn, execSync } = require('child_process');
4
+ const { parseArgs } = require('node:util');
4
5
  const path = require('path');
5
6
  const fs = require('fs');
6
7
  const os = require('os');
@@ -8,12 +9,358 @@ const https = require('https');
8
9
 
9
10
  const PACKAGE_NAME = 'olly-molly';
10
11
  const REPO = 'ruucm/olly-molly';
11
- const APP_DIR = path.join(os.homedir(), '.olly-molly');
12
- const DB_DIR = path.join(APP_DIR, 'db');
13
12
  const SOURCE_TARBALL_URL = `https://github.com/${REPO}/archive/refs/heads/main.tar.gz`;
14
13
  const RELEASE_BASE_URL = `https://github.com/${REPO}/releases/download`;
15
14
 
16
- console.log('\n🐙 Olly Molly\n');
15
+ // CLI argument definitions (no defaults for string options to allow env var fallback)
16
+ const CLI_OPTIONS = {
17
+ // Server settings
18
+ port: { type: 'string', short: 'p' },
19
+ host: { type: 'string', short: 'H' },
20
+ 'no-open': { type: 'boolean', default: false },
21
+ // Data/path settings
22
+ 'data-dir': { type: 'string', short: 'd' },
23
+ 'db-path': { type: 'string' },
24
+ // Development/debugging
25
+ dev: { type: 'boolean', default: false },
26
+ verbose: { type: 'boolean', short: 'v', default: false },
27
+ version: { type: 'boolean', short: 'V', default: false },
28
+ help: { type: 'boolean', short: 'h', default: false },
29
+ // Advanced options
30
+ reset: { type: 'boolean', default: false },
31
+ 'export-db': { type: 'string' },
32
+ 'import-db': { type: 'string' },
33
+ };
34
+
35
+ // Parse CLI arguments
36
+ let args;
37
+ try {
38
+ const result = parseArgs({ options: CLI_OPTIONS, allowPositionals: false });
39
+ args = result.values;
40
+ } catch (err) {
41
+ console.error(`❌ ${err.message}`);
42
+ console.error('Run "olly-molly --help" for usage information.');
43
+ process.exit(1);
44
+ }
45
+
46
+ // Get package version
47
+ function getPackageVersion() {
48
+ try {
49
+ return require('../package.json').version;
50
+ } catch {
51
+ return 'unknown';
52
+ }
53
+ }
54
+
55
+ // Show help message
56
+ function showHelp() {
57
+ console.log(`
58
+ 🐙 Olly Molly - Your AI Development Team
59
+
60
+ USAGE
61
+ olly-molly [options]
62
+
63
+ SERVER SETTINGS
64
+ -p, --port <port> Server port (default: 1234)
65
+ -H, --host <host> Binding host (default: localhost)
66
+ --no-open Disable auto browser open
67
+
68
+ DATA/PATH SETTINGS
69
+ -d, --data-dir <path> App data directory (default: ~/.olly-molly)
70
+ --db-path <path> Database path (default: <data-dir>/db)
71
+
72
+ DEVELOPMENT
73
+ --dev Run in development mode (next dev)
74
+ -v, --verbose Enable verbose logging
75
+
76
+ ADVANCED OPTIONS
77
+ --reset Reset all app data (with confirmation)
78
+ --export-db <path> Export database to zip file
79
+ --import-db <path> Import database from zip file
80
+
81
+ INFO
82
+ -V, --version Show version and exit
83
+ -h, --help Show this help and exit
84
+
85
+ ENVIRONMENT VARIABLES
86
+ OLLY_MOLLY_PORT Server port (fallback)
87
+ OLLY_MOLLY_HOST Binding host (fallback)
88
+ OLLY_MOLLY_DATA_DIR App data directory (fallback)
89
+ OLLY_MOLLY_DB_PATH Database path (fallback)
90
+
91
+ EXAMPLES
92
+ olly-molly # Start with defaults
93
+ olly-molly -p 3000 # Use port 3000
94
+ olly-molly --host 0.0.0.0 # Bind to all interfaces
95
+ olly-molly --dev -v # Development mode with verbose
96
+ olly-molly --export-db backup.zip # Export database
97
+ `);
98
+ }
99
+
100
+ // Handle immediate flags
101
+ if (args.help) {
102
+ showHelp();
103
+ process.exit(0);
104
+ }
105
+
106
+ if (args.version) {
107
+ console.log(`olly-molly v${getPackageVersion()}`);
108
+ process.exit(0);
109
+ }
110
+
111
+ // Configuration with priority: CLI args > env vars > defaults
112
+ function getConfig() {
113
+ const dataDir = args['data-dir']
114
+ || process.env.OLLY_MOLLY_DATA_DIR
115
+ || path.join(os.homedir(), '.olly-molly');
116
+
117
+ const port = args.port
118
+ || process.env.OLLY_MOLLY_PORT
119
+ || '1234';
120
+
121
+ // Validate port
122
+ const portNum = parseInt(port, 10);
123
+ if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
124
+ console.error(`❌ Port must be a number between 1-65535, got: ${port}`);
125
+ process.exit(1);
126
+ }
127
+
128
+ const host = args.host
129
+ || process.env.OLLY_MOLLY_HOST
130
+ || 'localhost';
131
+
132
+ const dbPath = args['db-path']
133
+ || process.env.OLLY_MOLLY_DB_PATH
134
+ || path.join(dataDir, 'db');
135
+
136
+ return {
137
+ APP_DIR: dataDir,
138
+ DB_DIR: dbPath,
139
+ CUSTOM_PROFILES_DIR: path.join(dataDir, 'custom-profiles'),
140
+ PORT: port,
141
+ HOST: host,
142
+ OPEN_BROWSER: !args['no-open'],
143
+ DEV_MODE: args.dev,
144
+ VERBOSE: args.verbose,
145
+ };
146
+ }
147
+
148
+ // Verbose logging utility
149
+ function verbose(config, ...messages) {
150
+ if (config.VERBOSE) {
151
+ console.log('🔍', ...messages);
152
+ }
153
+ }
154
+
155
+ // Handle --reset
156
+ async function handleReset(config) {
157
+ const readline = require('readline');
158
+ const rl = readline.createInterface({
159
+ input: process.stdin,
160
+ output: process.stdout,
161
+ });
162
+
163
+ return new Promise((resolve) => {
164
+ console.log(`\n⚠️ This will delete all data in: ${config.APP_DIR}`);
165
+ console.log(' Including: database, custom profiles, and application files.\n');
166
+
167
+ rl.question('Are you sure? Type "yes" to confirm: ', (answer) => {
168
+ rl.close();
169
+ if (answer.toLowerCase() === 'yes') {
170
+ if (fs.existsSync(config.APP_DIR)) {
171
+ fs.rmSync(config.APP_DIR, { recursive: true, force: true });
172
+ console.log('✅ All data has been reset.');
173
+ } else {
174
+ console.log('ℹ️ No data directory found.');
175
+ }
176
+ } else {
177
+ console.log('❌ Reset cancelled.');
178
+ }
179
+ resolve();
180
+ });
181
+ });
182
+ }
183
+
184
+ // Handle --export-db
185
+ async function handleExportDb(exportPath, config) {
186
+ const absPath = path.resolve(exportPath);
187
+ const parentDir = path.dirname(absPath);
188
+
189
+ if (!fs.existsSync(parentDir)) {
190
+ console.error(`❌ Export directory does not exist: ${parentDir}`);
191
+ process.exit(1);
192
+ }
193
+
194
+ if (!fs.existsSync(config.DB_DIR) && !fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
195
+ console.error('❌ No data to export. Database and profiles directories do not exist.');
196
+ process.exit(1);
197
+ }
198
+
199
+ console.log('📦 Exporting database...');
200
+ verbose(config, `DB_DIR: ${config.DB_DIR}`);
201
+ verbose(config, `CUSTOM_PROFILES_DIR: ${config.CUSTOM_PROFILES_DIR}`);
202
+
203
+ // Create tar.gz using native tar command
204
+ const tmpDir = path.join(os.tmpdir(), `olly-molly-export-${Date.now()}`);
205
+ fs.mkdirSync(tmpDir, { recursive: true });
206
+
207
+ try {
208
+ // Copy data to temp directory
209
+ if (fs.existsSync(config.DB_DIR)) {
210
+ fs.cpSync(config.DB_DIR, path.join(tmpDir, 'db'), { recursive: true });
211
+ }
212
+ if (fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
213
+ fs.cpSync(config.CUSTOM_PROFILES_DIR, path.join(tmpDir, 'custom-profiles'), { recursive: true });
214
+ }
215
+
216
+ // Create tar.gz
217
+ execSync(`tar -czf "${absPath}" -C "${tmpDir}" .`, { stdio: 'pipe' });
218
+ console.log(`✅ Database exported to: ${absPath}`);
219
+ } finally {
220
+ fs.rmSync(tmpDir, { recursive: true, force: true });
221
+ }
222
+ }
223
+
224
+ // Handle --import-db
225
+ async function handleImportDb(importPath, config) {
226
+ const absPath = path.resolve(importPath);
227
+
228
+ if (!fs.existsSync(absPath)) {
229
+ console.error(`❌ Import file not found: ${absPath}`);
230
+ process.exit(1);
231
+ }
232
+
233
+ console.log('📥 Importing database...');
234
+ verbose(config, `Import from: ${absPath}`);
235
+
236
+ // Backup existing data
237
+ const backupDir = path.join(os.tmpdir(), `olly-molly-import-backup-${Date.now()}`);
238
+ let hasBackup = false;
239
+
240
+ if (fs.existsSync(config.DB_DIR) || fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
241
+ console.log('📋 Backing up existing data...');
242
+ fs.mkdirSync(backupDir, { recursive: true });
243
+ if (fs.existsSync(config.DB_DIR)) {
244
+ fs.cpSync(config.DB_DIR, path.join(backupDir, 'db'), { recursive: true });
245
+ }
246
+ if (fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
247
+ fs.cpSync(config.CUSTOM_PROFILES_DIR, path.join(backupDir, 'custom-profiles'), { recursive: true });
248
+ }
249
+ hasBackup = true;
250
+ }
251
+
252
+ try {
253
+ // Extract to temp dir first to validate
254
+ const tmpDir = path.join(os.tmpdir(), `olly-molly-import-${Date.now()}`);
255
+ fs.mkdirSync(tmpDir, { recursive: true });
256
+ execSync(`tar -xzf "${absPath}" -C "${tmpDir}"`, { stdio: 'pipe' });
257
+
258
+ // Check if valid export
259
+ const hasDb = fs.existsSync(path.join(tmpDir, 'db'));
260
+ const hasProfiles = fs.existsSync(path.join(tmpDir, 'custom-profiles'));
261
+
262
+ if (!hasDb && !hasProfiles) {
263
+ fs.rmSync(tmpDir, { recursive: true, force: true });
264
+ console.error('❌ Invalid import file: no db or custom-profiles found.');
265
+ process.exit(1);
266
+ }
267
+
268
+ // Remove existing and copy new data
269
+ if (hasDb) {
270
+ if (fs.existsSync(config.DB_DIR)) {
271
+ fs.rmSync(config.DB_DIR, { recursive: true, force: true });
272
+ }
273
+ fs.mkdirSync(path.dirname(config.DB_DIR), { recursive: true });
274
+ fs.cpSync(path.join(tmpDir, 'db'), config.DB_DIR, { recursive: true });
275
+ }
276
+
277
+ if (hasProfiles) {
278
+ if (fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
279
+ fs.rmSync(config.CUSTOM_PROFILES_DIR, { recursive: true, force: true });
280
+ }
281
+ fs.mkdirSync(path.dirname(config.CUSTOM_PROFILES_DIR), { recursive: true });
282
+ fs.cpSync(path.join(tmpDir, 'custom-profiles'), config.CUSTOM_PROFILES_DIR, { recursive: true });
283
+ }
284
+
285
+ fs.rmSync(tmpDir, { recursive: true, force: true });
286
+
287
+ // Clean backup on success
288
+ if (hasBackup) {
289
+ fs.rmSync(backupDir, { recursive: true, force: true });
290
+ }
291
+
292
+ console.log('✅ Database imported successfully.');
293
+ } catch (err) {
294
+ // Restore backup on failure
295
+ if (hasBackup) {
296
+ console.log('⚠️ Import failed, restoring backup...');
297
+ if (fs.existsSync(path.join(backupDir, 'db'))) {
298
+ fs.cpSync(path.join(backupDir, 'db'), config.DB_DIR, { recursive: true });
299
+ }
300
+ if (fs.existsSync(path.join(backupDir, 'custom-profiles'))) {
301
+ fs.cpSync(path.join(backupDir, 'custom-profiles'), config.CUSTOM_PROFILES_DIR, { recursive: true });
302
+ }
303
+ fs.rmSync(backupDir, { recursive: true, force: true });
304
+ }
305
+ throw err;
306
+ }
307
+ }
308
+
309
+ // Handle --dev mode
310
+ async function handleDevMode(config) {
311
+ // Check if source exists (not just prebuilt)
312
+ const packageJsonPath = path.join(config.APP_DIR, 'package.json');
313
+ const srcExists = fs.existsSync(path.join(config.APP_DIR, 'app')) ||
314
+ fs.existsSync(path.join(config.APP_DIR, 'src'));
315
+
316
+ if (!fs.existsSync(packageJsonPath) || !srcExists) {
317
+ console.log('📥 Downloading source for development...');
318
+ if (fs.existsSync(config.APP_DIR)) {
319
+ // Backup user data
320
+ const backupDir = backupUserData(config);
321
+ fs.rmSync(config.APP_DIR, { recursive: true, force: true });
322
+ await download(SOURCE_TARBALL_URL, config.APP_DIR, { stripComponents: 1 });
323
+ restoreUserData(backupDir, config);
324
+ } else {
325
+ await download(SOURCE_TARBALL_URL, config.APP_DIR, { stripComponents: 1 });
326
+ }
327
+ console.log('✅ Source downloaded\n');
328
+ }
329
+
330
+ // Install full dependencies (not --omit=dev)
331
+ if (!fs.existsSync(path.join(config.APP_DIR, 'node_modules'))) {
332
+ console.log('📦 Installing dependencies...\n');
333
+ execSync('npm install', { cwd: config.APP_DIR, stdio: 'inherit' });
334
+ }
335
+
336
+ const displayHost = config.HOST === '0.0.0.0' ? 'localhost' : config.HOST;
337
+ console.log(`\n🚀 http://${displayHost}:${config.PORT}\n`);
338
+
339
+ // Auto-open browser
340
+ if (config.OPEN_BROWSER) {
341
+ setTimeout(() => {
342
+ const url = `http://${displayHost}:${config.PORT}`;
343
+ const cmd = process.platform === 'darwin' ? 'open'
344
+ : process.platform === 'win32' ? 'start'
345
+ : 'xdg-open';
346
+ try {
347
+ execSync(`${cmd} ${url}`, { stdio: 'ignore' });
348
+ } catch {}
349
+ }, 3000);
350
+ }
351
+
352
+ // Run next dev
353
+ const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
354
+ const server = spawn(npxCmd, ['next', 'dev', '--port', config.PORT, '--hostname', config.HOST], {
355
+ cwd: config.APP_DIR,
356
+ stdio: 'inherit',
357
+ shell: false,
358
+ });
359
+
360
+ server.on('close', (code) => process.exit(code || 0));
361
+ process.on('SIGINT', () => server.kill('SIGINT'));
362
+ process.on('SIGTERM', () => server.kill('SIGTERM'));
363
+ }
17
364
 
18
365
  // Get latest version from npm registry
19
366
  function getNpmVersion() {
@@ -80,103 +427,136 @@ function download(url, destDir, { allowNotFound = false, stripComponents = 1 } =
80
427
  });
81
428
  }
82
429
 
83
- function getLocalVersion() {
430
+ function getLocalVersion(appDir) {
84
431
  try {
85
- return JSON.parse(fs.readFileSync(path.join(APP_DIR, 'package.json'), 'utf8')).version;
432
+ return JSON.parse(fs.readFileSync(path.join(appDir, 'package.json'), 'utf8')).version;
86
433
  } catch { return null; }
87
434
  }
88
435
 
89
- const CUSTOM_PROFILES_DIR = path.join(APP_DIR, 'custom-profiles');
90
-
91
- function hasProductionBuild(standaloneServerPath) {
92
- return fs.existsSync(path.join(APP_DIR, '.next', 'BUILD_ID')) ||
436
+ function hasProductionBuild(appDir, standaloneServerPath) {
437
+ return fs.existsSync(path.join(appDir, '.next', 'BUILD_ID')) ||
93
438
  fs.existsSync(standaloneServerPath);
94
439
  }
95
440
 
96
- function backupUserData() {
441
+ function backupUserData(config) {
97
442
  const backupDir = path.join(os.tmpdir(), 'olly-molly-backup');
98
443
  fs.mkdirSync(backupDir, { recursive: true });
99
-
444
+
100
445
  // Backup database
101
- if (fs.existsSync(DB_DIR)) {
446
+ if (fs.existsSync(config.DB_DIR)) {
102
447
  const dbBackupDir = path.join(backupDir, 'db');
103
- fs.cpSync(DB_DIR, dbBackupDir, { recursive: true });
448
+ fs.cpSync(config.DB_DIR, dbBackupDir, { recursive: true });
104
449
  }
105
-
450
+
106
451
  // Backup custom profile images
107
- if (fs.existsSync(CUSTOM_PROFILES_DIR)) {
452
+ if (fs.existsSync(config.CUSTOM_PROFILES_DIR)) {
108
453
  const profilesBackupDir = path.join(backupDir, 'custom-profiles');
109
- fs.cpSync(CUSTOM_PROFILES_DIR, profilesBackupDir, { recursive: true });
454
+ fs.cpSync(config.CUSTOM_PROFILES_DIR, profilesBackupDir, { recursive: true });
110
455
  }
111
-
456
+
112
457
  return backupDir;
113
458
  }
114
459
 
115
- function restoreUserData(backupDir) {
460
+ function restoreUserData(backupDir, config) {
116
461
  if (!backupDir || !fs.existsSync(backupDir)) return;
117
-
462
+
118
463
  // Restore database
119
464
  const dbBackupDir = path.join(backupDir, 'db');
120
465
  if (fs.existsSync(dbBackupDir)) {
121
- fs.mkdirSync(DB_DIR, { recursive: true });
466
+ fs.mkdirSync(config.DB_DIR, { recursive: true });
122
467
  for (const file of fs.readdirSync(dbBackupDir)) {
123
468
  if (file.includes('.sqlite')) {
124
- fs.copyFileSync(path.join(dbBackupDir, file), path.join(DB_DIR, file));
469
+ fs.copyFileSync(path.join(dbBackupDir, file), path.join(config.DB_DIR, file));
125
470
  }
126
471
  }
127
472
  }
128
-
473
+
129
474
  // Restore custom profile images
130
475
  const profilesBackupDir = path.join(backupDir, 'custom-profiles');
131
476
  if (fs.existsSync(profilesBackupDir)) {
132
- fs.mkdirSync(CUSTOM_PROFILES_DIR, { recursive: true });
477
+ fs.mkdirSync(config.CUSTOM_PROFILES_DIR, { recursive: true });
133
478
  for (const file of fs.readdirSync(profilesBackupDir)) {
134
- fs.copyFileSync(path.join(profilesBackupDir, file), path.join(CUSTOM_PROFILES_DIR, file));
479
+ fs.copyFileSync(path.join(profilesBackupDir, file), path.join(config.CUSTOM_PROFILES_DIR, file));
135
480
  }
136
481
  }
137
-
482
+
138
483
  // Clean up backup
139
484
  fs.rmSync(backupDir, { recursive: true, force: true });
140
485
  }
141
486
 
142
487
  async function main() {
488
+ console.log('\n🐙 Olly Molly\n');
489
+
490
+ const config = getConfig();
491
+
492
+ verbose(config, 'Configuration:');
493
+ verbose(config, ` APP_DIR: ${config.APP_DIR}`);
494
+ verbose(config, ` DB_DIR: ${config.DB_DIR}`);
495
+ verbose(config, ` PORT: ${config.PORT}`);
496
+ verbose(config, ` HOST: ${config.HOST}`);
497
+
498
+ // Handle mutually exclusive operations
499
+ if (args.reset) {
500
+ await handleReset(config);
501
+ process.exit(0);
502
+ }
503
+
504
+ if (args['export-db']) {
505
+ await handleExportDb(args['export-db'], config);
506
+ process.exit(0);
507
+ }
508
+
509
+ if (args['import-db']) {
510
+ await handleImportDb(args['import-db'], config);
511
+ process.exit(0);
512
+ }
513
+
514
+ // Handle dev mode
515
+ if (config.DEV_MODE) {
516
+ await handleDevMode(config);
517
+ return;
518
+ }
519
+
520
+ // Regular startup flow
143
521
  let needsInstall = false;
144
522
  let needsBuild = false;
145
523
  let usedPrebuilt = false;
146
524
 
147
- const localVersion = getLocalVersion();
525
+ const localVersion = getLocalVersion(config.APP_DIR);
148
526
  const npmVersion = await getNpmVersion();
149
527
  const prebuiltUrl = getPrebuiltUrl(npmVersion);
150
- const standaloneServerPath = path.join(APP_DIR, '.next', 'standalone', 'server.js');
528
+ const standaloneServerPath = path.join(config.APP_DIR, '.next', 'standalone', 'server.js');
151
529
  const prebuiltOnDisk = () => fs.existsSync(standaloneServerPath);
152
530
 
153
531
  async function downloadApp() {
154
532
  if (prebuiltUrl) {
155
- const ok = await download(prebuiltUrl, APP_DIR, { allowNotFound: true, stripComponents: 0 });
533
+ verbose(config, `Trying prebuilt: ${prebuiltUrl}`);
534
+ const ok = await download(prebuiltUrl, config.APP_DIR, { allowNotFound: true, stripComponents: 0 });
156
535
  if (ok) {
157
536
  usedPrebuilt = true;
158
537
  return;
159
538
  }
160
539
  }
161
- await download(SOURCE_TARBALL_URL, APP_DIR, { stripComponents: 1 });
540
+ verbose(config, `Downloading source: ${SOURCE_TARBALL_URL}`);
541
+ await download(SOURCE_TARBALL_URL, config.APP_DIR, { stripComponents: 1 });
162
542
  usedPrebuilt = false;
163
543
  }
164
544
 
165
545
  // Update if npm version is newer
166
546
  if (localVersion && npmVersion && localVersion !== npmVersion) {
167
547
  console.log(`🔄 Updating ${localVersion} → ${npmVersion}\n`);
168
- const userDataBackup = backupUserData();
169
- fs.rmSync(APP_DIR, { recursive: true, force: true });
548
+ const userDataBackup = backupUserData(config);
549
+ fs.rmSync(config.APP_DIR, { recursive: true, force: true });
170
550
  console.log('📥 Downloading...');
171
551
  await downloadApp();
172
552
  console.log('✅ Downloaded\n');
173
- restoreUserData(userDataBackup);
553
+ restoreUserData(userDataBackup, config);
174
554
  needsInstall = !usedPrebuilt;
175
555
  needsBuild = !usedPrebuilt;
176
556
  }
177
557
 
178
558
  // First time
179
- if (!fs.existsSync(APP_DIR)) {
559
+ if (!fs.existsSync(config.APP_DIR)) {
180
560
  console.log('📥 Downloading...');
181
561
  await downloadApp();
182
562
  console.log('✅ Downloaded\n');
@@ -189,9 +569,9 @@ async function main() {
189
569
  }
190
570
 
191
571
  // Install
192
- if (needsInstall || !fs.existsSync(path.join(APP_DIR, 'node_modules'))) {
572
+ if (needsInstall || !fs.existsSync(path.join(config.APP_DIR, 'node_modules'))) {
193
573
  console.log('📦 Installing...\n');
194
- execSync('npm install --omit=dev', { cwd: APP_DIR, stdio: 'inherit' });
574
+ execSync('npm install --omit=dev', { cwd: config.APP_DIR, stdio: 'inherit' });
195
575
  }
196
576
 
197
577
  if (usedPrebuilt && !prebuiltOnDisk()) {
@@ -201,36 +581,39 @@ async function main() {
201
581
  }
202
582
 
203
583
  // Build
204
- if (needsBuild || !hasProductionBuild(standaloneServerPath)) {
584
+ if (needsBuild || !hasProductionBuild(config.APP_DIR, standaloneServerPath)) {
205
585
  console.log('\n🔨 Building...\n');
206
- execSync('npm run build', { cwd: APP_DIR, stdio: 'inherit' });
586
+ execSync('npm run build', { cwd: config.APP_DIR, stdio: 'inherit' });
207
587
  }
208
588
 
209
- console.log('\n🚀 http://localhost:1234\n');
589
+ const displayHost = config.HOST === '0.0.0.0' ? 'localhost' : config.HOST;
590
+ console.log(`\n🚀 http://${displayHost}:${config.PORT}\n`);
210
591
 
211
592
  // Auto-open browser after a short delay
212
- setTimeout(() => {
213
- const url = 'http://localhost:1234';
214
- const cmd = process.platform === 'darwin' ? 'open'
215
- : process.platform === 'win32' ? 'start'
216
- : 'xdg-open';
217
- try {
218
- execSync(`${cmd} ${url}`, { stdio: 'ignore' });
219
- } catch {}
220
- }, 2000);
593
+ if (config.OPEN_BROWSER) {
594
+ setTimeout(() => {
595
+ const url = `http://${displayHost}:${config.PORT}`;
596
+ const cmd = process.platform === 'darwin' ? 'open'
597
+ : process.platform === 'win32' ? 'start'
598
+ : 'xdg-open';
599
+ try {
600
+ execSync(`${cmd} ${url}`, { stdio: 'ignore' });
601
+ } catch {}
602
+ }, 2000);
603
+ }
221
604
 
222
605
  let server;
223
606
  if (usedPrebuilt) {
224
607
  server = spawn('node', [standaloneServerPath], {
225
- cwd: APP_DIR,
608
+ cwd: config.APP_DIR,
226
609
  stdio: 'inherit',
227
- env: { ...process.env, PORT: '1234' },
610
+ env: { ...process.env, PORT: config.PORT, HOSTNAME: config.HOST },
228
611
  shell: false
229
612
  });
230
613
  } else {
231
614
  const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
232
- server = spawn(npxCmd, ['next', 'start', '--port', '1234'], {
233
- cwd: APP_DIR, stdio: 'inherit', shell: false
615
+ server = spawn(npxCmd, ['next', 'start', '--port', config.PORT, '--hostname', config.HOST], {
616
+ cwd: config.APP_DIR, stdio: 'inherit', shell: false
234
617
  });
235
618
  }
236
619
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "olly-molly",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Your AI Development Team, Running Locally - Manage AI agents (PM, Frontend, Backend, QA) from a beautiful kanban board",
5
5
  "keywords": [
6
6
  "ai",