deckide 3.1.0 → 3.2.0

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 (2) hide show
  1. package/bin/deckide.js +285 -34
  2. package/package.json +1 -1
package/bin/deckide.js CHANGED
@@ -3,64 +3,315 @@
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import path from 'node:path';
5
5
  import os from 'node:os';
6
+ import fs from 'node:fs';
6
7
  import { execSync } from 'node:child_process';
8
+ import crypto from 'node:crypto';
7
9
 
8
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const dataDir = path.join(os.homedir(), '.deckide');
12
+ const settingsFile = path.join(dataDir, 'settings.json');
13
+
14
+ // ─── Settings helpers ───────────────────────────────────────────
15
+
16
+ function loadSettings() {
17
+ try {
18
+ return JSON.parse(fs.readFileSync(settingsFile, 'utf-8'));
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
23
+
24
+ function saveSettings(settings) {
25
+ fs.mkdirSync(dataDir, { recursive: true });
26
+ fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
27
+ }
28
+
29
+ // ─── CLI ────────────────────────────────────────────────────────
9
30
 
10
- // Parse CLI arguments
11
31
  const args = process.argv.slice(2);
12
- const options = {
13
- port: 8787,
14
- host: '0.0.0.0',
32
+ const command = args[0];
33
+
34
+ // ── deckide version ──
35
+ if (command === '--version' || command === '-v') {
36
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
37
+ console.log(pkg.version);
38
+ process.exit(0);
39
+ }
40
+
41
+ // ── deckide help ──
42
+ if (command === '--help' || command === '-h' || command === 'help') {
43
+ console.log(`
44
+ Deck IDE - Browser-based IDE
45
+
46
+ Usage:
47
+ deckide Start the server
48
+ deckide config Show all settings
49
+ deckide config set <key> <val> Set a config value
50
+ deckide config get <key> Get a config value
51
+ deckide config reset Reset all settings
52
+ deckide auth on Enable basic auth (interactive)
53
+ deckide auth off Disable basic auth
54
+ deckide auth status Show auth status
55
+ deckide status Show server status
56
+ deckide stop Stop running server
57
+
58
+ Start options:
59
+ -p, --port <port> Port to listen on
60
+ --host <host> Host to bind to
61
+ --no-open Don't open browser
62
+
63
+ Config keys:
64
+ port Server port (default: 8787)
65
+ host Bind host (default: 0.0.0.0)
66
+ cors CORS origin
67
+ maxFileSize Max file size in bytes
68
+ trustProxy Trust proxy headers (true/false)
69
+ `);
70
+ process.exit(0);
71
+ }
72
+
73
+ // ── deckide config ──
74
+ if (command === 'config') {
75
+ const sub = args[1];
76
+ const settings = loadSettings();
77
+
78
+ if (!sub || sub === 'list') {
79
+ // Show all config
80
+ if (Object.keys(settings).length === 0) {
81
+ console.log('No custom settings. Using defaults.');
82
+ console.log(' port: 8787');
83
+ console.log(' host: 0.0.0.0');
84
+ } else {
85
+ for (const [key, value] of Object.entries(settings)) {
86
+ if (key === 'basicAuthPassword' && value) {
87
+ console.log(` ${key}: ********`);
88
+ } else {
89
+ console.log(` ${key}: ${value}`);
90
+ }
91
+ }
92
+ }
93
+ process.exit(0);
94
+ }
95
+
96
+ if (sub === 'get') {
97
+ const key = args[2];
98
+ if (!key) {
99
+ console.error('Usage: deckide config get <key>');
100
+ process.exit(1);
101
+ }
102
+ const val = settings[key];
103
+ if (val === undefined) {
104
+ console.log(`${key}: (not set)`);
105
+ } else if (key === 'basicAuthPassword') {
106
+ console.log(`${key}: ********`);
107
+ } else {
108
+ console.log(`${key}: ${val}`);
109
+ }
110
+ process.exit(0);
111
+ }
112
+
113
+ if (sub === 'set') {
114
+ const key = args[2];
115
+ let value = args[3];
116
+ if (!key || value === undefined) {
117
+ console.error('Usage: deckide config set <key> <value>');
118
+ process.exit(1);
119
+ }
120
+ // Type coercion
121
+ if (value === 'true') value = true;
122
+ else if (value === 'false') value = false;
123
+ else if (/^\d+$/.test(value)) value = parseInt(value, 10);
124
+
125
+ settings[key] = value;
126
+ saveSettings(settings);
127
+ console.log(`${key} = ${key === 'basicAuthPassword' ? '********' : value}`);
128
+ process.exit(0);
129
+ }
130
+
131
+ if (sub === 'reset') {
132
+ saveSettings({});
133
+ console.log('Settings reset to defaults.');
134
+ process.exit(0);
135
+ }
136
+
137
+ console.error(`Unknown config command: ${sub}`);
138
+ process.exit(1);
139
+ }
140
+
141
+ // ── deckide auth ──
142
+ if (command === 'auth') {
143
+ const sub = args[1];
144
+ const settings = loadSettings();
145
+
146
+ if (sub === 'status') {
147
+ if (settings.basicAuthEnabled) {
148
+ console.log('Basic auth: enabled');
149
+ console.log(` user: ${settings.basicAuthUser || '(not set)'}`);
150
+ console.log(` password: ${settings.basicAuthPassword ? '********' : '(not set)'}`);
151
+ } else {
152
+ console.log('Basic auth: disabled');
153
+ }
154
+ process.exit(0);
155
+ }
156
+
157
+ if (sub === 'off') {
158
+ settings.basicAuthEnabled = false;
159
+ delete settings.basicAuthUser;
160
+ delete settings.basicAuthPassword;
161
+ saveSettings(settings);
162
+ console.log('Basic auth disabled.');
163
+ process.exit(0);
164
+ }
165
+
166
+ if (sub === 'on') {
167
+ const user = args[2];
168
+ const password = args[3];
169
+
170
+ if (!user || !password) {
171
+ // Generate random password if not provided
172
+ const genUser = user || 'admin';
173
+ const genPassword = crypto.randomBytes(16).toString('base64url');
174
+ settings.basicAuthEnabled = true;
175
+ settings.basicAuthUser = genUser;
176
+ settings.basicAuthPassword = genPassword;
177
+ saveSettings(settings);
178
+ console.log('Basic auth enabled.');
179
+ console.log(` user: ${genUser}`);
180
+ console.log(` password: ${genPassword}`);
181
+ console.log('');
182
+ console.log('Restart the server for changes to take effect.');
183
+ process.exit(0);
184
+ }
185
+
186
+ if (password.length < 8) {
187
+ console.error('Error: password must be at least 8 characters.');
188
+ process.exit(1);
189
+ }
190
+
191
+ settings.basicAuthEnabled = true;
192
+ settings.basicAuthUser = user;
193
+ settings.basicAuthPassword = password;
194
+ saveSettings(settings);
195
+ console.log('Basic auth enabled.');
196
+ console.log(` user: ${user}`);
197
+ console.log('Restart the server for changes to take effect.');
198
+ process.exit(0);
199
+ }
200
+
201
+ if (!sub) {
202
+ // Default to status
203
+ const enabled = settings.basicAuthEnabled;
204
+ if (enabled) {
205
+ console.log('Basic auth: enabled');
206
+ console.log(` user: ${settings.basicAuthUser || '(not set)'}`);
207
+ } else {
208
+ console.log('Basic auth: disabled');
209
+ }
210
+ console.log('');
211
+ console.log('Usage:');
212
+ console.log(' deckide auth on [user] [password] Enable auth');
213
+ console.log(' deckide auth off Disable auth');
214
+ console.log(' deckide auth status Show status');
215
+ process.exit(0);
216
+ }
217
+
218
+ console.error(`Unknown auth command: ${sub}`);
219
+ process.exit(1);
220
+ }
221
+
222
+ // ── deckide status ──
223
+ if (command === 'status') {
224
+ const settings = loadSettings();
225
+ const daemonInfoPath = path.join(dataDir, 'pty-daemon.json');
226
+
227
+ console.log('Deck IDE status');
228
+ console.log(` data dir: ${dataDir}`);
229
+ console.log(` port: ${settings.port || 8787}`);
230
+ console.log(` auth: ${settings.basicAuthEnabled ? 'enabled' : 'disabled'}`);
231
+
232
+ // Check if server is running
233
+ const port = settings.port || 8787;
234
+ try {
235
+ const res = execSync(`curl -s -o /dev/null -w "%{http_code}" http://localhost:${port}/health`, {
236
+ timeout: 3000,
237
+ }).toString().trim();
238
+ console.log(` server: running (port ${port})`);
239
+ } catch {
240
+ console.log(' server: not running');
241
+ }
242
+
243
+ // Check PTY daemon
244
+ if (fs.existsSync(daemonInfoPath)) {
245
+ try {
246
+ const info = JSON.parse(fs.readFileSync(daemonInfoPath, 'utf-8'));
247
+ console.log(` pty daemon: running (pid ${info.pid}, port ${info.port})`);
248
+ } catch {
249
+ console.log(' pty daemon: unknown');
250
+ }
251
+ } else {
252
+ console.log(' pty daemon: not running');
253
+ }
254
+
255
+ process.exit(0);
256
+ }
257
+
258
+ // ── deckide stop ──
259
+ if (command === 'stop') {
260
+ const settings = loadSettings();
261
+ const port = settings.port || 8787;
262
+ try {
263
+ execSync(`curl -s -X POST http://localhost:${port}/api/shutdown -H "Content-Type: application/json" -d '{"terminateDaemon":true}'`, {
264
+ timeout: 5000,
265
+ });
266
+ console.log('Server stopped.');
267
+ } catch {
268
+ console.log('Server is not running or could not be reached.');
269
+ }
270
+ process.exit(0);
271
+ }
272
+
273
+ // ── deckide (start server) ──
274
+
275
+ // Parse start options
276
+ const startOptions = {
277
+ port: null,
278
+ host: null,
15
279
  open: true,
16
280
  };
17
281
 
18
282
  for (let i = 0; i < args.length; i++) {
19
283
  const arg = args[i];
20
284
  if ((arg === '--port' || arg === '-p') && args[i + 1]) {
21
- options.port = parseInt(args[i + 1], 10);
285
+ startOptions.port = parseInt(args[i + 1], 10);
22
286
  i++;
23
287
  } else if (arg === '--host' && args[i + 1]) {
24
- options.host = args[i + 1];
288
+ startOptions.host = args[i + 1];
25
289
  i++;
26
290
  } else if (arg === '--no-open') {
27
- options.open = false;
28
- } else if (arg === '--help' || arg === '-h') {
29
- console.log(`
30
- Deck IDE - Browser-based IDE
31
-
32
- Usage:
33
- deckide [options]
34
-
35
- Options:
36
- -p, --port <port> Port to listen on (default: 8787)
37
- --host <host> Host to bind to (default: 0.0.0.0)
38
- --no-open Don't open browser automatically
39
- -h, --help Show this help message
40
- -v, --version Show version
41
- `);
42
- process.exit(0);
43
- } else if (arg === '--version' || arg === '-v') {
44
- const pkg = await import(path.join(__dirname, '..', 'package.json'), { with: { type: 'json' } });
45
- console.log(pkg.default.version);
46
- process.exit(0);
291
+ startOptions.open = false;
292
+ } else if (arg && !arg.startsWith('-')) {
293
+ console.error(`Unknown command: ${arg}`);
294
+ console.error('Run "deckide help" for usage.');
295
+ process.exit(1);
47
296
  }
48
297
  }
49
298
 
50
- // Set data directory to ~/.deckide/
51
- const dataDir = path.join(os.homedir(), '.deckide');
299
+ // Load settings and apply CLI overrides
300
+ const settings = loadSettings();
301
+ const port = startOptions.port || settings.port || 8787;
302
+ const host = startOptions.host || settings.host || '0.0.0.0';
303
+
52
304
  process.env.DECKIDE_DATA_DIR = dataDir;
53
- process.env.PORT = String(options.port);
54
- process.env.HOST = options.host;
305
+ process.env.PORT = String(port);
306
+ process.env.HOST = host;
55
307
 
56
308
  // Import and start the server
57
309
  const { createServer } = await import(path.join(__dirname, '..', 'dist', 'server.js'));
58
-
59
- const server = await createServer();
310
+ await createServer();
60
311
 
61
312
  // Open browser after server starts
62
- if (options.open) {
63
- const url = `http://localhost:${options.port}`;
313
+ if (startOptions.open) {
314
+ const url = `http://localhost:${port}`;
64
315
  setTimeout(() => {
65
316
  try {
66
317
  const platform = process.platform;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deckide",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Deck IDE - Browser-based IDE with terminal, file explorer, and git integration",
5
5
  "type": "module",
6
6
  "bin": {