dsh-switchman 1.5.0 → 1.7.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.de.md +45 -55
- package/README.es.md +21 -31
- package/README.fr.md +23 -33
- package/README.it.md +23 -33
- package/README.ja.md +42 -52
- package/README.ko.md +43 -53
- package/README.md +41 -51
- package/README.pt.md +23 -33
- package/README.ru.md +21 -31
- package/README.zh-TW.md +35 -45
- package/README.zh.md +25 -35
- package/client.js +1464 -43
- package/cordis.patch.yml +77 -92
- package/host/config.js +8 -0
- package/host/context-watch.js +197 -56
- package/host/routes.js +6 -2
- package/host/subagent-model.js +15 -10
- package/host/subagents.js +86 -70
- package/host/teams.js +160 -7
- package/package.json +2 -2
- package/skills/db-query/package.json +1 -1
- package/skills/db-query/scripts/lib/cli.js +23 -23
- package/skills/db-query/scripts/lib/config.js +78 -61
- package/skills/db-query/scripts/lib/mysql-readonly.js +167 -158
- package/skills/db-query/scripts/lib/output.js +56 -55
- package/skills/db-query/scripts/lib/redis-readonly.js +116 -111
- package/skills/db-query/scripts/mysql-query.js +46 -42
- package/skills/db-query/scripts/redis-query.js +32 -29
- package/skills/db-query/scripts/setup.sh +16 -16
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Redis
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Redis read-only query entry point
|
|
6
|
+
* Pipeline: CLI parsing → command allowlist validation → connect → sendCommand → output → close
|
|
7
|
+
* Security: the db is selected only via connection config (the SELECT command is not exposed);
|
|
8
|
+
* a client-side timer destroys the socket on timeout;
|
|
9
|
+
* a Redis ACL read-only user is the final server-side boundary
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const { createClient } = require('redis');
|
|
13
13
|
|
|
14
|
-
// ──
|
|
14
|
+
// ── Internal modules ──
|
|
15
15
|
const { parseCli, CliError } = require('./lib/cli');
|
|
16
16
|
const { loadEnvFile, resolveEnv, resolveEnvNumber, ConfigError, sanitizeTarget, safeMessage, buildTlsOptions } = require('./lib/config');
|
|
17
17
|
const { validateRedisCommand, RedisReadonlyError } = require('./lib/redis-readonly');
|
|
18
18
|
const { formatRedisTable, formatJson } = require('./lib/output');
|
|
19
19
|
|
|
20
|
-
// ── CLI
|
|
20
|
+
// ── CLI option spec ──
|
|
21
21
|
const REDIS_SPEC = {
|
|
22
22
|
host: { type: 'string', default: '127.0.0.1' },
|
|
23
23
|
port: { type: 'integer', default: 6379, min: 1, max: 65535 },
|
|
@@ -38,32 +38,33 @@ const REDIS_SPEC = {
|
|
|
38
38
|
|
|
39
39
|
const ENV_PREFIX = 'DB_QUERY_REDIS_';
|
|
40
40
|
|
|
41
|
-
// ──
|
|
41
|
+
// ── Main ──
|
|
42
42
|
async function main() {
|
|
43
43
|
const { options, rest } = parseCli(process.argv.slice(2), REDIS_SPEC);
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// A Redis command must follow --
|
|
46
46
|
if (rest.length === 0) {
|
|
47
|
-
throw new CliError('
|
|
47
|
+
throw new CliError('A Redis command must follow -- (e.g. -- GET key)');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
//
|
|
50
|
+
// Command allowlist validation (before any connection setup)
|
|
51
51
|
const validated = validateRedisCommand(rest, {
|
|
52
52
|
maxItems: options.maxItems,
|
|
53
53
|
allowUnbounded: options['allow-unbounded'],
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
// --validate-only
|
|
56
|
+
// --validate-only: pure offline validation, no connection
|
|
57
57
|
if (options['validate-only']) {
|
|
58
|
-
process.stderr.write(`✓
|
|
59
|
-
process.stderr.write(
|
|
58
|
+
process.stderr.write(`✓ Command validation passed: ${validated.command} ${validated.args.join(' ')}\n`);
|
|
59
|
+
process.stderr.write(`Output shape: ${validated.shape}${validated.unbounded ? ' (unbounded)' : ''}\n`);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// env
|
|
63
|
+
// Load env file
|
|
64
64
|
const envPairs = loadEnvFile(options['env-file']);
|
|
65
65
|
|
|
66
|
-
//
|
|
66
|
+
// Merge environment overrides (resolveEnvNumber uniformly enforces bounds,
|
|
67
|
+
// so env files cannot bypass hard limits)
|
|
67
68
|
const host = resolveEnv(envPairs, ENV_PREFIX + 'HOST', options.host);
|
|
68
69
|
const port = resolveEnvNumber(envPairs, ENV_PREFIX + 'PORT', options.port, { min: 1, max: 65535 });
|
|
69
70
|
const user = resolveEnv(envPairs, ENV_PREFIX + 'USER', options.user);
|
|
@@ -78,11 +79,11 @@ async function main() {
|
|
|
78
79
|
const connectTimeoutMs = resolveEnvNumber(envPairs, ENV_PREFIX + 'CONNECT_TIMEOUT_MS', options['connect-timeout-ms'], { min: 1000, max: 30000 });
|
|
79
80
|
const connectTimeoutMsResolved = connectTimeoutMs;
|
|
80
81
|
|
|
81
|
-
//
|
|
82
|
+
// Print sanitized target
|
|
82
83
|
const target = sanitizeTarget({ host, port, user, database: db });
|
|
83
84
|
process.stderr.write(`→ Redis ${target}\n`);
|
|
84
85
|
|
|
85
|
-
//
|
|
86
|
+
// Build the node-redis v4 client
|
|
86
87
|
const clientConfig = {
|
|
87
88
|
socket: {
|
|
88
89
|
host,
|
|
@@ -96,22 +97,23 @@ async function main() {
|
|
|
96
97
|
database: db,
|
|
97
98
|
password: password || undefined,
|
|
98
99
|
username: user,
|
|
99
|
-
//
|
|
100
|
+
// Keep Buffer results to avoid losing binary data via auto stringification
|
|
100
101
|
returnBuffers: true,
|
|
101
102
|
};
|
|
102
103
|
|
|
103
104
|
const client = createClient(clientConfig);
|
|
104
105
|
|
|
105
|
-
//
|
|
106
|
+
// Timeout timer (covers connect + sendCommand)
|
|
106
107
|
const timer = setTimeout(() => {
|
|
107
108
|
client.destroy();
|
|
108
|
-
//
|
|
109
|
+
// Do not reject directly (the promise may already be settled); handled via the error event
|
|
109
110
|
}, timeoutMsResolved);
|
|
110
111
|
|
|
111
112
|
try {
|
|
112
113
|
await client.connect();
|
|
113
114
|
|
|
114
|
-
// sendCommand
|
|
115
|
+
// sendCommand takes args as an array; never join-then-split
|
|
116
|
+
// (prevents truncation of arguments containing spaces)
|
|
115
117
|
const cmdArgs = [validated.command, ...validated.args.map(String)];
|
|
116
118
|
const startMs = Date.now();
|
|
117
119
|
const result = await client.sendCommand(cmdArgs);
|
|
@@ -119,7 +121,7 @@ async function main() {
|
|
|
119
121
|
|
|
120
122
|
clearTimeout(timer);
|
|
121
123
|
|
|
122
|
-
//
|
|
124
|
+
// Detect truncation
|
|
123
125
|
let count = 0;
|
|
124
126
|
let truncated = false;
|
|
125
127
|
if (Array.isArray(result)) {
|
|
@@ -130,14 +132,14 @@ async function main() {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
if (options.format === 'json') {
|
|
133
|
-
// JSON envelope
|
|
135
|
+
// JSON envelope to stdout
|
|
134
136
|
process.stdout.write(JSON.stringify({
|
|
135
137
|
source: 'redis',
|
|
136
138
|
target,
|
|
137
139
|
elapsedMs,
|
|
138
140
|
count: truncated ? maxItemsResolved : count,
|
|
139
141
|
truncated,
|
|
140
|
-
// Buffer → base64
|
|
142
|
+
// Buffer → base64 to keep the JSON safe
|
|
141
143
|
result: sanitizeForJson(result),
|
|
142
144
|
}, null, 2) + '\n');
|
|
143
145
|
} else {
|
|
@@ -149,16 +151,16 @@ async function main() {
|
|
|
149
151
|
}) + '\n');
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
process.stderr.write(`✓ ${count}
|
|
154
|
+
process.stderr.write(`✓ ${count} item(s)${truncated ? ' (truncated)' : ''} in ${elapsedMs}ms\n`);
|
|
153
155
|
} catch (e) {
|
|
154
156
|
clearTimeout(timer);
|
|
155
157
|
throw e;
|
|
156
158
|
} finally {
|
|
157
|
-
try { await client.quit(); } catch (_) { /*
|
|
159
|
+
try { await client.quit(); } catch (_) { /* ignore close errors */ }
|
|
158
160
|
}
|
|
159
161
|
}
|
|
160
162
|
|
|
161
|
-
/**
|
|
163
|
+
/** Recursively convert Buffers to base64 to keep the JSON safe */
|
|
162
164
|
function sanitizeForJson(val) {
|
|
163
165
|
if (Buffer.isBuffer(val)) return val.toString('base64');
|
|
164
166
|
if (Array.isArray(val)) return val.map(sanitizeForJson);
|
|
@@ -171,7 +173,8 @@ function sanitizeForJson(val) {
|
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
main().catch(e => {
|
|
174
|
-
//
|
|
176
|
+
// Validation/config errors show their message directly (not connection
|
|
177
|
+
// errors, no sanitizing needed)
|
|
175
178
|
if (e instanceof CliError || e instanceof RedisReadonlyError || e instanceof ConfigError) {
|
|
176
179
|
process.stderr.write('✗ ' + e.message + '\n');
|
|
177
180
|
} else {
|
|
@@ -1,51 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# db-query
|
|
3
|
-
#
|
|
2
|
+
# db-query skill: one-shot environment check + install + test
|
|
3
|
+
# Idempotent: skip npm ci when dependencies are already installed
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
7
|
SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
8
|
cd "$SCRIPT_DIR"
|
|
9
9
|
|
|
10
|
-
# ── Node / npm
|
|
10
|
+
# ── Node / npm version check ──
|
|
11
11
|
if ! command -v node &>/dev/null; then
|
|
12
|
-
echo "✗
|
|
12
|
+
echo "✗ node not found; Node.js >= 18 is required"
|
|
13
13
|
exit 1
|
|
14
14
|
fi
|
|
15
15
|
|
|
16
16
|
NODE_MAJOR=$(node -e "process.stdout.write(String(process.versions.node.split('.')[0]))")
|
|
17
17
|
if [ "$NODE_MAJOR" -lt 18 ]; then
|
|
18
|
-
echo "✗ Node.js
|
|
18
|
+
echo "✗ Node.js version $(node -v) is below the minimum required 18"
|
|
19
19
|
exit 1
|
|
20
20
|
fi
|
|
21
21
|
|
|
22
22
|
if ! command -v npm &>/dev/null; then
|
|
23
|
-
echo "✗
|
|
23
|
+
echo "✗ npm not found"
|
|
24
24
|
exit 1
|
|
25
25
|
fi
|
|
26
26
|
|
|
27
27
|
echo "Node $(node -v) ✓ npm $(npm -v) ✓"
|
|
28
28
|
|
|
29
|
-
# ──
|
|
30
|
-
#
|
|
29
|
+
# ── Idempotent install of runtime dependencies ──
|
|
30
|
+
# When node_modules already exists, npm ci would still verify consistency, so skipping is idempotent-safe
|
|
31
31
|
if [ -d node_modules ]; then
|
|
32
|
-
echo "
|
|
32
|
+
echo "Dependencies already present, skipping install (delete node_modules to force a reinstall)"
|
|
33
33
|
else
|
|
34
|
-
echo "
|
|
34
|
+
echo "Installing runtime dependencies (mysql2 redis dotenv)..."
|
|
35
35
|
if [ -f package-lock.json ]; then
|
|
36
36
|
npm ci --omit=dev --no-audit --no-fund
|
|
37
37
|
else
|
|
38
38
|
npm install --omit=dev --no-audit --no-fund
|
|
39
39
|
fi
|
|
40
|
-
echo "
|
|
40
|
+
echo "Dependencies installed ✓"
|
|
41
41
|
fi
|
|
42
42
|
|
|
43
|
-
# ──
|
|
44
|
-
echo "
|
|
43
|
+
# ── Run tests ──
|
|
44
|
+
echo "Running tests..."
|
|
45
45
|
if node --test tests/*.test.js 2>/dev/null; then
|
|
46
|
-
echo "
|
|
46
|
+
echo "All tests passed ✓"
|
|
47
47
|
else
|
|
48
|
-
echo "⚠
|
|
48
|
+
echo "⚠ No test files found or tests did not pass (no tests on first deployment is normal)"
|
|
49
49
|
fi
|
|
50
50
|
|
|
51
|
-
echo "db-query
|
|
51
|
+
echo "db-query environment ready ✓"
|