@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.
- package/README.md +155 -0
- package/dist/index.js +761 -0
- 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
|
+
}
|