@scriptdb/cli 1.0.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 (3) hide show
  1. package/README.md +155 -0
  2. package/dist/index.js +761 -0
  3. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # ScriptDB CLI
2
+
3
+ Command-line interface for ScriptDB - Database management with TypeScript scripting.
4
+
5
+ ## Installation
6
+
7
+ ### From Binary (Info Only)
8
+
9
+ Pre-built binaries are available for download:
10
+
11
+ - **Linux x64**: `scriptdb-linux-x64`
12
+ - **Linux ARM64**: `scriptdb-linux-arm64`
13
+ - **Linux x64 (musl)**: `scriptdb-linux-x64-musl`
14
+ - **Linux ARM64 (musl)**: `scriptdb-linux-arm64-musl`
15
+ - **macOS x64 (Intel)**: `scriptdb-darwin-x64`
16
+ - **macOS ARM64 (Apple Silicon)**: `scriptdb-darwin-arm64`
17
+ - **Windows x64**: `scriptdb-windows-x64.exe`
18
+
19
+ **Note**: These binaries are standalone info-only builds. They show help/version but don't include server functionality. For full functionality, install from source.
20
+
21
+ Make it executable (Linux/macOS):
22
+ ```bash
23
+ chmod +x scriptdb-*
24
+ sudo mv scriptdb-* /usr/local/bin/scriptdb
25
+ ```
26
+
27
+ ### From Source (Recommended for Full Functionality)
28
+
29
+ ```bash
30
+ bun install
31
+ bun run build:all
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ### Server Management
37
+
38
+ Start the server in daemon mode (background):
39
+ ```bash
40
+ scriptdb start -d
41
+ ```
42
+
43
+ Start the server in foreground:
44
+ ```bash
45
+ scriptdb start
46
+ ```
47
+
48
+ Stop the server:
49
+ ```bash
50
+ scriptdb stop
51
+ ```
52
+
53
+ Restart the server:
54
+ ```bash
55
+ scriptdb restart -d
56
+ ```
57
+
58
+ Check server status:
59
+ ```bash
60
+ scriptdb status
61
+ ```
62
+
63
+ View server logs:
64
+ ```bash
65
+ scriptdb logs
66
+ ```
67
+
68
+ ### Interactive Shell
69
+
70
+ Start the interactive shell (requires server to be running):
71
+ ```bash
72
+ scriptdb shell
73
+ ```
74
+
75
+ Shell commands:
76
+ - `.exit`, `.quit` - Exit the shell
77
+ - `.help` - Show help
78
+ - `.dbs` - List all databases
79
+ - `.use <name>` - Switch to a database
80
+ - `.create <name>` - Create a new database
81
+
82
+ Execute TypeScript code:
83
+ ```bash
84
+ > const x = 1 + 1
85
+ > x
86
+ 2
87
+ > [1,2,3].map(n => n * 2)
88
+ [2, 4, 6]
89
+ ```
90
+
91
+ ### Configuration
92
+
93
+ Configuration file: `~/.scriptdb/config.json`
94
+
95
+ Example:
96
+ ```json
97
+ {
98
+ "host": "localhost",
99
+ "port": 1234,
100
+ "users": [
101
+ {
102
+ "username": "admin",
103
+ "password": "your-password",
104
+ "hash": false
105
+ }
106
+ ],
107
+ "folder": "databases",
108
+ "secure": false
109
+ }
110
+ ```
111
+
112
+ ## Building
113
+
114
+ ### Build for all platforms (local)
115
+
116
+ ```bash
117
+ bun run build:binary
118
+ ```
119
+
120
+ ### Build using Docker (recommended for cross-platform)
121
+
122
+ ```bash
123
+ bun run build:docker
124
+ ```
125
+
126
+ This will build binaries for all platforms inside a Docker container.
127
+
128
+ ### Build for specific platform
129
+
130
+ ```bash
131
+ bun run build:linux-x64
132
+ bun run build:linux-arm64
133
+ bun run build:windows-x64
134
+ bun run build:darwin-x64
135
+ bun run build:darwin-arm64
136
+ bun run build:linux-x64-musl
137
+ bun run build:linux-arm64-musl
138
+ ```
139
+
140
+ ## Development
141
+
142
+ ```bash
143
+ bun run dev
144
+ ```
145
+
146
+ ## Files
147
+
148
+ - PID file: `~/.scriptdb/scriptdb.pid`
149
+ - Log file: `~/.scriptdb/scriptdb.log`
150
+ - Config file: `~/.scriptdb/config.json`
151
+ - Databases: `~/.scriptdb/databases/`
152
+
153
+ ## License
154
+
155
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,761 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
+
5
+ // src/index.ts
6
+ import { server } from "@scriptdb/server";
7
+ import { spawn } from "child_process";
8
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+
12
+ // ../../node_modules/.bun/ps-list@9.0.0/node_modules/ps-list/index.js
13
+ import process2 from "node:process";
14
+ import fs from "node:fs";
15
+ import { promisify } from "node:util";
16
+ import path from "node:path";
17
+ import { fileURLToPath } from "node:url";
18
+ import childProcess from "node:child_process";
19
+ var __dirname2 = path.dirname(fileURLToPath(import.meta.url));
20
+ var DEFAULT_MAX_BUFFER = 64000000;
21
+ var MAXIMUM_PATH_COMBINATION_ATTEMPTS = 6;
22
+ var execFile = promisify(childProcess.execFile);
23
+ var PROCESS_FIELDS = {
24
+ CPU_PERCENT: "%cpu",
25
+ MEMORY_PERCENT: "%mem",
26
+ PROCESS_ID: "pid",
27
+ PARENT_PROCESS_ID: "ppid",
28
+ USER_ID: "uid",
29
+ START_TIME: "lstart",
30
+ COMMAND_NAME: "comm",
31
+ ARGUMENTS: "args"
32
+ };
33
+ var buildProcessCommandFlags = (includeAllUsersProcesses) => (includeAllUsersProcesses === false ? "" : "a") + "wwxo";
34
+ var makeStartTime = (startTimeString) => {
35
+ if (!startTimeString) {
36
+ return;
37
+ }
38
+ const parsedDate = new Date(startTimeString);
39
+ return Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate;
40
+ };
41
+ var extractExecutablePath = (commandLine) => {
42
+ if (!commandLine) {
43
+ return "";
44
+ }
45
+ if (!commandLine.startsWith("/") && !commandLine.startsWith('"')) {
46
+ return "";
47
+ }
48
+ if (commandLine.startsWith('"')) {
49
+ const quotedPathMatch = commandLine.match(/^"([^"]+)"/);
50
+ if (quotedPathMatch && fs.existsSync(quotedPathMatch[1])) {
51
+ return quotedPathMatch[1];
52
+ }
53
+ return "";
54
+ }
55
+ const commandParts = commandLine.split(" ");
56
+ const firstCommandToken = commandParts[0];
57
+ if (fs.existsSync(firstCommandToken)) {
58
+ return firstCommandToken;
59
+ }
60
+ const maximumCombinationAttempts = Math.min(commandParts.length, MAXIMUM_PATH_COMBINATION_ATTEMPTS);
61
+ for (let tokenCount = 2;tokenCount <= maximumCombinationAttempts; tokenCount++) {
62
+ const candidateExecutablePath = commandParts.slice(0, tokenCount).join(" ");
63
+ if (fs.existsSync(candidateExecutablePath)) {
64
+ return candidateExecutablePath;
65
+ }
66
+ }
67
+ return "";
68
+ };
69
+ var resolveExecutablePath = (operatingSystemPlatform, processId, commandLine) => {
70
+ if (operatingSystemPlatform === "linux" && processId) {
71
+ try {
72
+ const symbolicLink = fs.readlinkSync(`/proc/${processId}/exe`);
73
+ return symbolicLink.replace(/\s+\(deleted\)$/, "");
74
+ } catch {}
75
+ }
76
+ return extractExecutablePath(commandLine);
77
+ };
78
+ var parseNumericField = (fieldValue, parserFunction = Number.parseInt, defaultValue = 0) => {
79
+ if (!fieldValue) {
80
+ return defaultValue;
81
+ }
82
+ const parsedValue = parserFunction(fieldValue, 10);
83
+ return Number.isNaN(parsedValue) ? defaultValue : parsedValue;
84
+ };
85
+ var parseIntegerOrUndefined = (fieldValue) => {
86
+ if (fieldValue === undefined || fieldValue === "") {
87
+ return;
88
+ }
89
+ const parsedValue = Number.parseInt(fieldValue, 10);
90
+ return Number.isNaN(parsedValue) ? undefined : parsedValue;
91
+ };
92
+ var parseProcessFields = ({ processId, parentProcessId, userId, cpuUsage, memoryUsage, commandName, startTimeString, command }) => {
93
+ const parsedProcessId = parseNumericField(processId);
94
+ const parsedParentProcessId = parseNumericField(parentProcessId);
95
+ const parsedUserId = parseIntegerOrUndefined(userId);
96
+ const parsedCpuUsagePercentage = parseNumericField(cpuUsage, Number.parseFloat);
97
+ const parsedMemoryUsagePercentage = parseNumericField(memoryUsage, Number.parseFloat);
98
+ const resolvedExecutablePath = resolveExecutablePath(process2.platform, parsedProcessId, command);
99
+ const derivedProcessName = resolvedExecutablePath ? path.basename(resolvedExecutablePath) : commandName || "";
100
+ return {
101
+ pid: parsedProcessId,
102
+ ppid: parsedParentProcessId,
103
+ uid: parsedUserId,
104
+ cpu: parsedCpuUsagePercentage,
105
+ memory: parsedMemoryUsagePercentage,
106
+ name: derivedProcessName,
107
+ path: resolvedExecutablePath,
108
+ startTime: makeStartTime(startTimeString),
109
+ cmd: command || ""
110
+ };
111
+ };
112
+ var windows = async () => {
113
+ let binary;
114
+ switch (process2.arch) {
115
+ case "x64": {
116
+ binary = "fastlist-0.3.0-x64.exe";
117
+ break;
118
+ }
119
+ case "ia32": {
120
+ binary = "fastlist-0.3.0-x86.exe";
121
+ break;
122
+ }
123
+ case "arm64": {
124
+ throw new Error("Windows ARM64 is not supported yet.");
125
+ }
126
+ default: {
127
+ throw new Error(`Unsupported architecture: ${process2.arch}`);
128
+ }
129
+ }
130
+ const binaryPath = path.join(__dirname2, "vendor", binary);
131
+ const { stdout } = await execFile(binaryPath, {
132
+ maxBuffer: DEFAULT_MAX_BUFFER,
133
+ windowsHide: true,
134
+ encoding: "utf8"
135
+ });
136
+ return stdout.trim().split(/\r?\n/).map((line) => line.split("\t")).map(([processId, parentProcessId, processName]) => ({
137
+ pid: Number.parseInt(processId, 10),
138
+ ppid: Number.parseInt(parentProcessId, 10),
139
+ name: processName
140
+ }));
141
+ };
142
+ var nonWindowsFallbackMultipleCalls = async (options = {}) => {
143
+ const processDataByProcessId = {};
144
+ const processCommandFlags = buildProcessCommandFlags(options.all);
145
+ const fields = [
146
+ PROCESS_FIELDS.COMMAND_NAME,
147
+ PROCESS_FIELDS.ARGUMENTS,
148
+ PROCESS_FIELDS.PARENT_PROCESS_ID,
149
+ PROCESS_FIELDS.USER_ID,
150
+ PROCESS_FIELDS.CPU_PERCENT,
151
+ PROCESS_FIELDS.MEMORY_PERCENT,
152
+ PROCESS_FIELDS.START_TIME
153
+ ];
154
+ await Promise.all(fields.map(async (fieldName) => {
155
+ const { stdout } = await execFile("ps", [processCommandFlags, `${PROCESS_FIELDS.PROCESS_ID}=,${fieldName}=`], {
156
+ maxBuffer: DEFAULT_MAX_BUFFER,
157
+ encoding: "utf8",
158
+ env: {
159
+ ...process2.env,
160
+ LC_ALL: "C",
161
+ LANG: "C"
162
+ }
163
+ });
164
+ for (const line of stdout.trim().split(`
165
+ `)) {
166
+ const trimmedLine = line.trim();
167
+ const spaceIndex = trimmedLine.indexOf(" ");
168
+ if (spaceIndex === -1) {
169
+ const processId2 = trimmedLine;
170
+ const fieldValue2 = "";
171
+ processDataByProcessId[processId2] ??= {};
172
+ processDataByProcessId[processId2][fieldName] = fieldValue2;
173
+ continue;
174
+ }
175
+ const processId = trimmedLine.slice(0, spaceIndex);
176
+ const fieldValue = trimmedLine.slice(spaceIndex + 1).trim();
177
+ processDataByProcessId[processId] ??= {};
178
+ processDataByProcessId[processId][fieldName] = fieldValue;
179
+ }
180
+ }));
181
+ return Object.entries(processDataByProcessId).filter(([, data]) => data[PROCESS_FIELDS.COMMAND_NAME] && data[PROCESS_FIELDS.PARENT_PROCESS_ID] !== undefined).map(([processId, data]) => parseProcessFields({
182
+ processId,
183
+ parentProcessId: data[PROCESS_FIELDS.PARENT_PROCESS_ID],
184
+ userId: data[PROCESS_FIELDS.USER_ID],
185
+ cpuUsage: data[PROCESS_FIELDS.CPU_PERCENT],
186
+ memoryUsage: data[PROCESS_FIELDS.MEMORY_PERCENT],
187
+ commandName: data[PROCESS_FIELDS.COMMAND_NAME],
188
+ startTimeString: data[PROCESS_FIELDS.START_TIME],
189
+ command: data[PROCESS_FIELDS.ARGUMENTS] ?? ""
190
+ }));
191
+ };
192
+ var nonWindowsCall = async (options = {}) => {
193
+ const processCommandFlags = buildProcessCommandFlags(options.all);
194
+ const executeFileOptions = {
195
+ maxBuffer: DEFAULT_MAX_BUFFER,
196
+ encoding: "utf8",
197
+ env: {
198
+ ...process2.env,
199
+ LC_ALL: "C",
200
+ LANG: "C"
201
+ }
202
+ };
203
+ const processFieldCommaSeparatedList = [
204
+ PROCESS_FIELDS.PROCESS_ID,
205
+ PROCESS_FIELDS.PARENT_PROCESS_ID,
206
+ PROCESS_FIELDS.USER_ID,
207
+ PROCESS_FIELDS.CPU_PERCENT,
208
+ PROCESS_FIELDS.MEMORY_PERCENT,
209
+ PROCESS_FIELDS.START_TIME,
210
+ PROCESS_FIELDS.COMMAND_NAME
211
+ ].map((fieldName) => `${fieldName}=`).join(",");
212
+ const commandFieldCommaSeparatedList = [
213
+ PROCESS_FIELDS.PROCESS_ID,
214
+ PROCESS_FIELDS.ARGUMENTS
215
+ ].map((fieldName) => `${fieldName}=`).join(",");
216
+ const processListingPromises = [
217
+ execFile("ps", [processCommandFlags, processFieldCommaSeparatedList], executeFileOptions),
218
+ execFile("ps", [processCommandFlags, commandFieldCommaSeparatedList], executeFileOptions)
219
+ ];
220
+ const [processOutput, commandOutput] = await Promise.all(processListingPromises);
221
+ const processLines = processOutput.stdout.trim().split(`
222
+ `);
223
+ const commandLines = commandOutput.stdout.trim().split(`
224
+ `);
225
+ const commandLinesByProcessId = {};
226
+ for (const line of commandLines) {
227
+ const trimmedLine = line.trim();
228
+ const spaceIndex = trimmedLine.indexOf(" ");
229
+ if (spaceIndex === -1) {
230
+ const processId2 = trimmedLine;
231
+ commandLinesByProcessId[processId2] = "";
232
+ continue;
233
+ }
234
+ const processId = trimmedLine.slice(0, spaceIndex);
235
+ const command = trimmedLine.slice(spaceIndex + 1).trim();
236
+ commandLinesByProcessId[processId] = command;
237
+ }
238
+ const processes = [];
239
+ for (const line of processLines) {
240
+ const trimmedLine = line.trim();
241
+ if (!trimmedLine) {
242
+ continue;
243
+ }
244
+ const processLineRegexMatch = trimmedLine.match(/^(\d+)\s+(\d+)\s+(\d+)\s+([\d.]+)\s+([\d.]+)\s+(.+)/);
245
+ if (!processLineRegexMatch) {
246
+ continue;
247
+ }
248
+ const [, processId, parentProcessId, userId, cpuUsage, memoryUsage, dateAndCommandPortion] = processLineRegexMatch;
249
+ const startTimeRegexMatch = dateAndCommandPortion.match(/^((?:\w{3}\s+){2}\d{1,2}\s+(?:\d{2}:){2}\d{2}\s+\d{4})\s+(.*)$/);
250
+ let startTimeString = "";
251
+ let processCommandName = dateAndCommandPortion;
252
+ if (startTimeRegexMatch) {
253
+ startTimeString = startTimeRegexMatch[1];
254
+ processCommandName = startTimeRegexMatch[2] || "";
255
+ }
256
+ processes.push(parseProcessFields({
257
+ processId,
258
+ parentProcessId,
259
+ userId,
260
+ cpuUsage,
261
+ memoryUsage,
262
+ commandName: processCommandName,
263
+ startTimeString,
264
+ command: commandLinesByProcessId[processId] ?? ""
265
+ }));
266
+ }
267
+ return processes;
268
+ };
269
+ var nonWindows = async (options = {}) => {
270
+ try {
271
+ return await nonWindowsCall(options);
272
+ } catch {
273
+ return nonWindowsFallbackMultipleCalls(options);
274
+ }
275
+ };
276
+ var psList = process2.platform === "win32" ? windows : nonWindows;
277
+ var ps_list_default = psList;
278
+
279
+ // src/shell.ts
280
+ import { createInterface } from "readline";
281
+ import Client from "@scriptdb/client";
282
+ async function startShell(options) {
283
+ const rl = createInterface({
284
+ input: process.stdin,
285
+ output: process.stdout,
286
+ prompt: "> "
287
+ });
288
+ console.log(`
289
+ ScriptDB Shell`);
290
+ console.log("==============");
291
+ console.log(`Connecting to ${options.host}:${options.port}...`);
292
+ const username = await question(rl, "Username: ");
293
+ const password = await question(rl, "Password: ", true);
294
+ let client;
295
+ let token;
296
+ try {
297
+ client = new Client(`scriptdb://${options.host}:${options.port}`, {
298
+ secure: options.secure || false,
299
+ logger: {
300
+ debug: () => {},
301
+ info: () => {},
302
+ warn: console.warn,
303
+ error: console.error
304
+ }
305
+ });
306
+ await client.connect();
307
+ const loginResult = await client.execute({
308
+ action: "login",
309
+ data: { username, password }
310
+ });
311
+ if (loginResult.error) {
312
+ console.error(`
313
+ ✗ Login failed:`, loginResult.error);
314
+ await client.close();
315
+ rl.close();
316
+ return;
317
+ }
318
+ token = loginResult.token;
319
+ console.log(`
320
+ ✓ Connected successfully!`);
321
+ console.log(`
322
+ Type your TypeScript code and press Enter to execute.`);
323
+ console.log("Commands: .exit, .quit, .help, .dbs, .use <name>, .create <name>");
324
+ console.log();
325
+ let currentDb;
326
+ rl.prompt();
327
+ rl.on("line", async (line) => {
328
+ const trimmed = line.trim();
329
+ if (!trimmed) {
330
+ rl.prompt();
331
+ return;
332
+ }
333
+ if (trimmed === ".exit" || trimmed === ".quit") {
334
+ console.log("Goodbye!");
335
+ await client.close();
336
+ rl.close();
337
+ return;
338
+ }
339
+ if (trimmed === ".help") {
340
+ printShellHelp();
341
+ rl.prompt();
342
+ return;
343
+ }
344
+ if (trimmed === ".dbs") {
345
+ try {
346
+ const result = await client.execute({
347
+ action: "list-dbs",
348
+ data: { token }
349
+ });
350
+ if (result.error) {
351
+ console.error("Error:", result.error);
352
+ } else {
353
+ console.log(`
354
+ Databases:`);
355
+ if (result.databases && result.databases.length > 0) {
356
+ result.databases.forEach((db) => {
357
+ const marker = db === currentDb ? " *" : "";
358
+ console.log(` - ${db}${marker}`);
359
+ });
360
+ } else {
361
+ console.log(" (no databases)");
362
+ }
363
+ console.log();
364
+ }
365
+ } catch (err) {
366
+ console.error("Error:", err);
367
+ }
368
+ rl.prompt();
369
+ return;
370
+ }
371
+ if (trimmed.startsWith(".use ")) {
372
+ currentDb = trimmed.substring(5).trim();
373
+ console.log(`✓ Using database: ${currentDb}`);
374
+ rl.prompt();
375
+ return;
376
+ }
377
+ if (trimmed.startsWith(".create ")) {
378
+ const dbName = trimmed.substring(8).trim();
379
+ try {
380
+ const result = await client.execute({
381
+ action: "create-db",
382
+ data: { token, databaseName: dbName }
383
+ });
384
+ if (result.error) {
385
+ console.error("Error:", result.error);
386
+ } else {
387
+ console.log(`✓ Created database: ${dbName}`);
388
+ currentDb = dbName;
389
+ }
390
+ } catch (err) {
391
+ console.error("Error:", err);
392
+ }
393
+ rl.prompt();
394
+ return;
395
+ }
396
+ if (!currentDb) {
397
+ console.error("Error: No database selected. Use .use <name> or .create <name>");
398
+ rl.prompt();
399
+ return;
400
+ }
401
+ try {
402
+ const result = await client.execute({
403
+ action: "script-code",
404
+ data: {
405
+ token,
406
+ databaseName: currentDb,
407
+ code: trimmed
408
+ }
409
+ });
410
+ if (result.error) {
411
+ console.error("Error:", result.error);
412
+ } else {
413
+ if (result.result !== undefined) {
414
+ console.log(formatResult(result.result));
415
+ }
416
+ }
417
+ } catch (err) {
418
+ console.error("Error:", err.message || err);
419
+ }
420
+ rl.prompt();
421
+ });
422
+ rl.on("close", async () => {
423
+ console.log(`
424
+ Goodbye!`);
425
+ await client.close();
426
+ process.exit(0);
427
+ });
428
+ } catch (err) {
429
+ console.error(`
430
+ ✗ Connection failed:`, err.message || err);
431
+ rl.close();
432
+ process.exit(1);
433
+ }
434
+ }
435
+ function question(rl, prompt, silent = false) {
436
+ return new Promise((resolve) => {
437
+ if (silent) {
438
+ const stdin = process.stdin;
439
+ stdin.setRawMode?.(true);
440
+ process.stdout.write(prompt);
441
+ let password = "";
442
+ stdin.on("data", function onData(char) {
443
+ const c = char.toString();
444
+ if (c === `
445
+ ` || c === "\r" || c === "\x04") {
446
+ stdin.setRawMode?.(false);
447
+ stdin.removeListener("data", onData);
448
+ process.stdout.write(`
449
+ `);
450
+ resolve(password);
451
+ } else if (c === "\x03") {
452
+ stdin.setRawMode?.(false);
453
+ process.exit(1);
454
+ } else if (c === "" || c === "\b") {
455
+ if (password.length > 0) {
456
+ password = password.slice(0, -1);
457
+ process.stdout.write("\b \b");
458
+ }
459
+ } else {
460
+ password += c;
461
+ process.stdout.write("*");
462
+ }
463
+ });
464
+ } else {
465
+ rl.question(prompt, (answer) => {
466
+ resolve(answer);
467
+ });
468
+ }
469
+ });
470
+ }
471
+ function formatResult(result) {
472
+ if (result === null)
473
+ return "null";
474
+ if (result === undefined)
475
+ return "undefined";
476
+ if (typeof result === "object") {
477
+ try {
478
+ return JSON.stringify(result, null, 2);
479
+ } catch {
480
+ return String(result);
481
+ }
482
+ }
483
+ return String(result);
484
+ }
485
+ function printShellHelp() {
486
+ console.log(`
487
+ Shell Commands:
488
+ .exit, .quit Exit the shell
489
+ .help Show this help
490
+ .dbs List all databases
491
+ .use <name> Switch to a database
492
+ .create <name> Create a new database
493
+
494
+ Execute TypeScript:
495
+ Just type your code and press Enter
496
+
497
+ Examples:
498
+ .create mydb
499
+ .use mydb
500
+ const x = 1 + 1
501
+ x
502
+ [1,2,3].map(n => n * 2)
503
+ `);
504
+ }
505
+
506
+ // src/index.ts
507
+ var PID_FILE = join(homedir(), ".scriptdb", "scriptdb.pid");
508
+ var LOG_FILE = join(homedir(), ".scriptdb", "scriptdb.log");
509
+ var CONFIG_FILE = join(homedir(), ".scriptdb", "config.json");
510
+ async function main() {
511
+ const args = process.argv.slice(2);
512
+ const command = args[0];
513
+ const isDaemon = args.includes("-d") || args.includes("--daemon");
514
+ if (!command || command === "start") {
515
+ await startCommand(isDaemon);
516
+ } else if (command === "stop") {
517
+ await stopCommand();
518
+ } else if (command === "restart") {
519
+ await restartCommand(isDaemon);
520
+ } else if (command === "status") {
521
+ await statusCommand();
522
+ } else if (command === "logs") {
523
+ showLogs();
524
+ } else if (command === "shell") {
525
+ await shellCommand();
526
+ } else if (command === "help" || command === "--help" || command === "-h") {
527
+ printHelp();
528
+ } else if (command === "version" || command === "--version" || command === "-v") {
529
+ printVersion();
530
+ } else {
531
+ console.error(`Unknown command: ${command}`);
532
+ console.error('Run "scriptdb help" for usage information');
533
+ process.exit(1);
534
+ }
535
+ }
536
+ async function startCommand(daemon) {
537
+ const running = await isServerRunning();
538
+ if (running) {
539
+ console.error("ScriptDB server is already running");
540
+ console.log('Run "scriptdb status" to check status');
541
+ console.log('Run "scriptdb stop" to stop the server');
542
+ process.exit(1);
543
+ }
544
+ if (daemon) {
545
+ console.log("Starting ScriptDB server in daemon mode...");
546
+ startDaemon();
547
+ } else {
548
+ console.log("Starting ScriptDB server...");
549
+ try {
550
+ await server();
551
+ } catch (err) {
552
+ console.error("Failed to start server:", err);
553
+ process.exit(1);
554
+ }
555
+ }
556
+ }
557
+ function startDaemon() {
558
+ const child = spawn(process.execPath, [process.argv[1], "_run_server"], {
559
+ detached: true,
560
+ stdio: ["ignore", "pipe", "pipe"]
561
+ });
562
+ const logStream = __require("fs").createWriteStream(LOG_FILE, { flags: "a" });
563
+ child.stdout?.pipe(logStream);
564
+ child.stderr?.pipe(logStream);
565
+ writeFileSync(PID_FILE, String(child.pid));
566
+ child.unref();
567
+ console.log(`ScriptDB server started with PID: ${child.pid}`);
568
+ console.log(`Logs: ${LOG_FILE}`);
569
+ console.log('Run "scriptdb status" to check status');
570
+ process.exit(0);
571
+ }
572
+ async function stopCommand() {
573
+ const running = await isServerRunning();
574
+ if (!running) {
575
+ console.log("ScriptDB server is not running");
576
+ return;
577
+ }
578
+ const pid = getPid();
579
+ if (!pid) {
580
+ console.error("Could not read PID file");
581
+ return;
582
+ }
583
+ try {
584
+ process.kill(pid, "SIGTERM");
585
+ console.log(`Stopping ScriptDB server (PID: ${pid})...`);
586
+ let attempts = 0;
587
+ while (attempts < 30) {
588
+ await new Promise((resolve) => setTimeout(resolve, 100));
589
+ try {
590
+ process.kill(pid, 0);
591
+ attempts++;
592
+ } catch {
593
+ break;
594
+ }
595
+ }
596
+ if (existsSync(PID_FILE)) {
597
+ unlinkSync(PID_FILE);
598
+ }
599
+ console.log("ScriptDB server stopped");
600
+ } catch (err) {
601
+ if (err.code === "ESRCH") {
602
+ console.log("ScriptDB server is not running");
603
+ if (existsSync(PID_FILE)) {
604
+ unlinkSync(PID_FILE);
605
+ }
606
+ } else {
607
+ console.error("Failed to stop server:", err);
608
+ process.exit(1);
609
+ }
610
+ }
611
+ }
612
+ async function restartCommand(daemon) {
613
+ console.log("Restarting ScriptDB server...");
614
+ await stopCommand();
615
+ await new Promise((resolve) => setTimeout(resolve, 1000));
616
+ await startCommand(daemon);
617
+ }
618
+ async function statusCommand() {
619
+ const running = await isServerRunning();
620
+ const pid = getPid();
621
+ if (running && pid) {
622
+ console.log("✓ ScriptDB server is running");
623
+ console.log(` PID: ${pid}`);
624
+ console.log(` Logs: ${LOG_FILE}`);
625
+ try {
626
+ const processes = await ps_list_default();
627
+ const proc = processes.find((p) => p.pid === pid);
628
+ if (proc) {
629
+ if (proc.cpu !== undefined) {
630
+ console.log(` CPU: ${proc.cpu.toFixed(1)}%`);
631
+ }
632
+ if (proc.memory !== undefined) {
633
+ console.log(` Memory: ${(proc.memory / 1024 / 1024).toFixed(1)} MB`);
634
+ }
635
+ }
636
+ } catch {}
637
+ } else {
638
+ console.log("✗ ScriptDB server is not running");
639
+ }
640
+ }
641
+ function showLogs() {
642
+ if (!existsSync(LOG_FILE)) {
643
+ console.log("No logs found");
644
+ return;
645
+ }
646
+ const logs = readFileSync(LOG_FILE, "utf-8");
647
+ console.log(logs);
648
+ }
649
+ async function shellCommand() {
650
+ const running = await isServerRunning();
651
+ if (!running) {
652
+ console.error("✗ ScriptDB server is not running");
653
+ console.log("Start the server first with: scriptdb start -d");
654
+ process.exit(1);
655
+ }
656
+ let host = "localhost";
657
+ let port = 1234;
658
+ let secure = false;
659
+ if (existsSync(CONFIG_FILE)) {
660
+ try {
661
+ const config = JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
662
+ if (config.host)
663
+ host = config.host;
664
+ if (config.port)
665
+ port = config.port;
666
+ if (config.secure)
667
+ secure = config.secure;
668
+ } catch {}
669
+ }
670
+ await startShell({ host, port, secure });
671
+ }
672
+ async function isServerRunning() {
673
+ const pid = getPid();
674
+ if (!pid)
675
+ return false;
676
+ try {
677
+ process.kill(pid, 0);
678
+ return true;
679
+ } catch {
680
+ return false;
681
+ }
682
+ }
683
+ function getPid() {
684
+ if (!existsSync(PID_FILE))
685
+ return null;
686
+ try {
687
+ const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim());
688
+ return isNaN(pid) ? null : pid;
689
+ } catch {
690
+ return null;
691
+ }
692
+ }
693
+ if (process.argv[2] === "_run_server") {
694
+ server().catch((err) => {
695
+ console.error("Server error:", err);
696
+ process.exit(1);
697
+ });
698
+ } else {
699
+ main().catch((err) => {
700
+ console.error("Unexpected error:", err);
701
+ process.exit(1);
702
+ });
703
+ }
704
+ function printHelp() {
705
+ console.log(`
706
+ ScriptDB CLI - Database management with TypeScript scripting
707
+
708
+ Usage:
709
+ scriptdb [command] [options]
710
+
711
+ Commands:
712
+ start Start the ScriptDB server
713
+ -d, --daemon Run in background (daemon mode)
714
+ stop Stop the running server
715
+ restart Restart the server
716
+ -d, --daemon Run in background after restart
717
+ status Check server status
718
+ logs Show server logs
719
+ shell Start interactive shell (requires server running)
720
+ help Show this help message
721
+ version Show version information
722
+
723
+ Examples:
724
+ scriptdb start -d Start server in background
725
+ scriptdb status Check if server is running
726
+ scriptdb shell Start interactive shell
727
+ scriptdb stop Stop the server
728
+ scriptdb restart -d Restart in background
729
+ scriptdb logs View server logs
730
+
731
+ Configuration:
732
+ Configuration file: ~/.scriptdb/config.json
733
+
734
+ Example config.json:
735
+ {
736
+ "host": "localhost",
737
+ "port": 1234,
738
+ "users": [
739
+ {
740
+ "username": "admin",
741
+ "password": "your-password",
742
+ "hash": false
743
+ }
744
+ ],
745
+ "folder": "databases",
746
+ "secure": false
747
+ }
748
+
749
+ Process Management:
750
+ - PID file: ~/.scriptdb/scriptdb.pid
751
+ - Log file: ~/.scriptdb/scriptdb.log
752
+ `);
753
+ }
754
+ function printVersion() {
755
+ const version = "1.0.0";
756
+ console.log(`ScriptDB CLI v${version}`);
757
+ }
758
+ main().catch((err) => {
759
+ console.error("Unexpected error:", err);
760
+ process.exit(1);
761
+ });
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@scriptdb/cli",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to start and manage ScriptDB server",
5
+ "type": "module",
6
+ "bin": {
7
+ "scriptdb": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "dev": "bun --watch src/index.ts",
16
+ "build": "bun build src/index.ts --outdir dist --target node --format esm --external '@scriptdb/*'",
17
+ "build:types": "tsc --emitDeclarationOnly",
18
+ "build:all": "bun run build && bun run build:types",
19
+ "test": "bun test",
20
+ "lint": "eslint src --ext .ts",
21
+ "lint:fix": "eslint src --ext .ts --fix",
22
+ "typecheck": "tsc --noEmit",
23
+ "clean": "rm -rf dist"
24
+ },
25
+ "keywords": [
26
+ "scriptdb",
27
+ "cli",
28
+ "server",
29
+ "database"
30
+ ],
31
+ "devDependencies": {
32
+ "@types/bun": "^1.3.2",
33
+ "@types/node": "^24.10.1",
34
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
35
+ "@typescript-eslint/parser": "^6.0.0",
36
+ "bun-types": "latest",
37
+ "eslint": "^8.0.0",
38
+ "typescript": "^5.0.0"
39
+ },
40
+ "dependencies": {
41
+ "@scriptdb/server": "^1.0.0",
42
+ "@scriptdb/client": "^1.0.0",
43
+ "ps-list": "^9.0.0"
44
+ }
45
+ }