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.
- package/bin/deckide.js +285 -34
- 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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
285
|
+
startOptions.port = parseInt(args[i + 1], 10);
|
|
22
286
|
i++;
|
|
23
287
|
} else if (arg === '--host' && args[i + 1]) {
|
|
24
|
-
|
|
288
|
+
startOptions.host = args[i + 1];
|
|
25
289
|
i++;
|
|
26
290
|
} else if (arg === '--no-open') {
|
|
27
|
-
|
|
28
|
-
} else if (arg
|
|
29
|
-
console.
|
|
30
|
-
|
|
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
|
-
//
|
|
51
|
-
const
|
|
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(
|
|
54
|
-
process.env.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 (
|
|
63
|
-
const url = `http://localhost:${
|
|
313
|
+
if (startOptions.open) {
|
|
314
|
+
const url = `http://localhost:${port}`;
|
|
64
315
|
setTimeout(() => {
|
|
65
316
|
try {
|
|
66
317
|
const platform = process.platform;
|