diff --git a/gui/electron/index.html b/gui/electron/index.html index 6fd3df0..7faaa62 100644 --- a/gui/electron/index.html +++ b/gui/electron/index.html @@ -3,6 +3,16 @@ VoxVera GUI +

VoxVera

@@ -20,6 +30,7 @@

+

   
 
 
diff --git a/gui/electron/main.js b/gui/electron/main.js
index 8c3946f..da471f3 100644
--- a/gui/electron/main.js
+++ b/gui/electron/main.js
@@ -42,21 +42,37 @@ ipcMain.handle('run-quickstart', async (_, config) => {
     );
     return -1;
   }
-  return new Promise((resolve, reject) => {
-    const proc = spawn(voxveraPath, ['--config', configPath, 'quickstart']);
+  return new Promise((resolve) => {
+    const args = ['--config', configPath, 'quickstart', '--non-interactive'];
+    const proc = spawn(voxveraPath, args);
     proc.stdout.on('data', data => {
       const line = data.toString();
       process.stdout.write(line);
+      if (mainWindow) {
+        mainWindow.webContents.send('log', { text: line, isError: false });
+      }
       const m = line.match(/Onion URL:\s*(https?:\/\/[a-z0-9.-]+\.onion)/i);
       if (m && mainWindow) {
         mainWindow.webContents.send('onion-url', m[1]);
       }
     });
     proc.stderr.on('data', data => {
-      process.stderr.write(data);
+      const line = data.toString();
+      process.stderr.write(line);
+      if (mainWindow) {
+        mainWindow.webContents.send('log', { text: line, isError: true });
+      }
+    });
+    proc.on('close', code => {
+      if (code !== 0) {
+        dialog.showErrorBox('voxvera error', `voxvera exited with code ${code}.`);
+      }
+      resolve(code);
+    });
+    proc.on('error', err => {
+      dialog.showErrorBox('voxvera error', err.message);
+      resolve(-1);
     });
-    proc.on('close', code => resolve(code));
-    proc.on('error', err => reject(err));
   });
 });
 
diff --git a/gui/electron/preload.js b/gui/electron/preload.js
index 43d8a8c..fefb889 100644
--- a/gui/electron/preload.js
+++ b/gui/electron/preload.js
@@ -3,5 +3,6 @@ const { contextBridge, ipcRenderer } = require('electron');
 contextBridge.exposeInMainWorld('voxvera', {
   loadConfig: () => ipcRenderer.invoke('load-config'),
   quickstart: (config) => ipcRenderer.invoke('run-quickstart', config),
-  onOnionUrl: (cb) => ipcRenderer.on('onion-url', (_, url) => cb(url))
+  onOnionUrl: (cb) => ipcRenderer.on('onion-url', (_, url) => cb(url)),
+  onLog: (cb) => ipcRenderer.on('log', (_, data) => cb(data.text, data.isError))
 });
diff --git a/voxvera/cli.py b/voxvera/cli.py
index 47943bb..0e47569 100644
--- a/voxvera/cli.py
+++ b/voxvera/cli.py
@@ -406,7 +406,9 @@ def main(argv=None):
 
     sub.add_parser('import', help='Batch import JSON files from imports/')
     sub.add_parser('serve', help='Serve flyer over OnionShare using config')
-    sub.add_parser('quickstart', help='Init, build and serve in sequence')
+    p_quickstart = sub.add_parser('quickstart', help='Init, build and serve in sequence')
+    p_quickstart.add_argument('--non-interactive', action='store_true',
+                              help='Skip interactive prompts and use existing config')
     sub.add_parser('check', help='Check for required external dependencies')
 
     args = parser.parse_args(argv)
@@ -429,7 +431,15 @@ def main(argv=None):
     elif args.command == 'import':
         import_configs()
     elif args.command == 'quickstart':
-        interactive_update(config_path)
+        if not args.non_interactive:
+            if not sys.stdin.isatty():
+                print(
+                    "Error: quickstart requires an interactive terminal. "
+                    "Use --non-interactive or run 'voxvera init' first.",
+                    file=sys.stderr,
+                )
+                sys.exit(1)
+            interactive_update(config_path)
         build_assets(config_path)
         serve(config_path)
     elif args.command == 'check':