diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c6ee85e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + - run: node --check src/server.js + - run: node --check src/server-manager.js diff --git a/src/auth.js b/src/auth.js index 8800b24..77043e2 100644 --- a/src/auth.js +++ b/src/auth.js @@ -6,15 +6,16 @@ class Auth { constructor() { this.storageKey = 'opencode-auth'; + this.password = null; } /** * Store credentials */ saveCredentials(username, password) { + this.password = password; const credentials = { username, - password: this.encode(password), timestamp: new Date().toISOString() }; localStorage.setItem(this.storageKey, JSON.stringify(credentials)); @@ -27,9 +28,10 @@ class Auth { try { const data = JSON.parse(localStorage.getItem(this.storageKey)); if (!data) return null; + if (!this.password) return null; return { username: data.username, - password: this.decode(data.password), + password: this.password, timestamp: data.timestamp }; } catch (e) { @@ -41,6 +43,7 @@ class Auth { * Clear credentials */ clearCredentials() { + this.password = null; localStorage.removeItem(this.storageKey); } @@ -51,20 +54,6 @@ class Auth { return this.getCredentials() !== null; } - /** - * Encode password (obfuscation, not encryption) - */ - encode(text) { - return btoa(encodeURIComponent(text)); - } - - /** - * Decode password - */ - decode(encoded) { - return decodeURIComponent(atob(encoded)); - } - /** * Verify server connection with credentials */ diff --git a/src/main.js b/src/main.js index c3b99ac..97cacde 100644 --- a/src/main.js +++ b/src/main.js @@ -15,9 +15,12 @@ class App { const savedConfig = { url: localStorage.getItem('opencode-url'), username: localStorage.getItem('opencode-username') || 'opencode', - password: localStorage.getItem('opencode-password') || '' + password: '' }; + // Remove credentials stored by older versions. Passwords now stay in memory only. + localStorage.removeItem('opencode-password'); + // Restore theme const savedTheme = localStorage.getItem('opencode-theme'); if (savedTheme) { diff --git a/src/server-manager.js b/src/server-manager.js index e2b182c..6b50c1e 100644 --- a/src/server-manager.js +++ b/src/server-manager.js @@ -3,8 +3,8 @@ * Run: node src/server-manager.js [start|stop|status] */ -const { OpenCodeServer } = require('./server.js'); -require('dotenv').config(); +import { OpenCodeServer } from './server.js'; +import 'dotenv/config'; const command = process.argv[2] || 'start'; const port = parseInt(process.env.OPENCODE_PORT || process.argv[3] || '4096', 10); @@ -32,7 +32,7 @@ async function main() { console.log(` http://localhost:${port}`); console.log(`\nšŸ” Credentials:`); console.log(` Username: ${username}`); - console.log(` Password: ${password}`); + console.log(' Password: configured via OPENCODE_SERVER_PASSWORD'); console.log('\nPress Ctrl+C to stop\n'); // Keep process alive diff --git a/src/server.js b/src/server.js index a3f1a9e..0eb8792 100644 --- a/src/server.js +++ b/src/server.js @@ -3,16 +3,16 @@ * Starts and manages the OpenCode web server */ -const { exec, spawn } = require('child_process'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import fs from 'node:fs'; +import os from 'node:os'; class OpenCodeServer { constructor(options = {}) { this.options = { port: options.port || 4096, - hostname: options.hostname || '0.0.0.0', + hostname: options.hostname || '127.0.0.1', password: options.password || process.env.OPENCODE_SERVER_PASSWORD, username: options.username || process.env.OPENCODE_SERVER_USERNAME || 'opencode', cors: options.cors || [], @@ -28,9 +28,13 @@ class OpenCodeServer { */ checkOpenCodeInstalled() { return new Promise((resolve) => { - exec('opencode --version', (error) => { - resolve(!error); + const openCodePath = this.getOpenCodePath(); + const child = spawn(openCodePath, ['--version'], { + stdio: 'ignore', + shell: process.platform === 'win32' && openCodePath.endsWith('.cmd') }); + child.once('error', () => resolve(false)); + child.once('exit', (code) => resolve(code === 0)); }); } @@ -79,8 +83,9 @@ class OpenCodeServer { const installed = await this.checkOpenCodeInstalled(); if (!installed) { - console.log('OpenCode not installed. Installing...'); - await this.installOpenCode(); + throw new Error( + 'OpenCode is not installed. Install it explicitly from the official OpenCode instructions before starting this server.' + ); } console.log('šŸš€ Starting OpenCode web server...'); @@ -133,51 +138,27 @@ class OpenCodeServer { } /** - * Install OpenCode + * Wait for server to be ready */ - installOpenCode() { - return new Promise((resolve, reject) => { - const install = spawn('bash', ['-c', 'curl -fsSL https://opencode.ai/install | bash'], { - stdio: 'inherit', - shell: false - }); + async waitForServer() { + const timeout = 10000; + const start = Date.now(); + const url = `http://${this.options.hostname}:${this.options.port}/doc`; - install.on('exit', (code) => { - if (code === 0) { - console.log('OpenCode installed successfully'); - resolve(); - } else { - reject(new Error('Failed to install OpenCode')); + while (Date.now() - start <= timeout) { + try { + const response = await fetch(url, { signal: AbortSignal.timeout(1500) }); + if (response.ok) { + console.log(`āœ… OpenCode server ready at http://${this.options.hostname}:${this.options.port}`); + return; } - }); - }); - } + } catch { + // Server is still starting. + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } - /** - * Wait for server to be ready - */ - waitForServer() { - return new Promise((resolve) => { - const timeout = 10000; - const start = Date.now(); - - const check = setInterval(() => { - const port = this.options.port; - const { exec } = require('child_process'); - - exec(`curl -s http://${this.options.hostname}:${port}/doc`, (error, stdout) => { - if (!error && stdout) { - clearInterval(check); - console.log(`āœ… OpenCode server ready at http://${this.options.hostname}:${port}`); - resolve(); - } else if (Date.now() - start > timeout) { - clearInterval(check); - console.log('āš ļø Server not ready within timeout, but may still be starting...'); - resolve(); - } - }); - }, 1000); - }); + console.log('āš ļø Server not ready within timeout, but may still be starting...'); } /** @@ -204,5 +185,4 @@ class OpenCodeServer { } } -// Export for CommonJS -module.exports = { OpenCodeServer }; \ No newline at end of file +export { OpenCodeServer }; \ No newline at end of file diff --git a/src/ui.js b/src/ui.js index 6be1935..bf82966 100644 --- a/src/ui.js +++ b/src/ui.js @@ -731,7 +731,7 @@ class OpenCodeUI {
- +