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
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* JSON
|
|
4
|
+
* Result output: table/JSON formatting + value normalization + truncation guards
|
|
5
|
+
* JSON goes to stdout and status to stderr so stdout stays valid JSON
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
// ──
|
|
8
|
+
// ── Value normalization ──
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* - BigInt →
|
|
13
|
-
* - Buffer/Uint8Array → UTF-8
|
|
14
|
-
* - Date → ISO
|
|
15
|
-
* -
|
|
11
|
+
* Normalize a single value into a safe output format
|
|
12
|
+
* - BigInt → decimal string
|
|
13
|
+
* - Buffer/Uint8Array → string if printable UTF-8, otherwise base64
|
|
14
|
+
* - Date → ISO string
|
|
15
|
+
* - other objects → safe JSON
|
|
16
16
|
*
|
|
17
17
|
* @param {*} val
|
|
18
18
|
* @param {object} opts - { maxValueBytes: number }
|
|
@@ -21,22 +21,23 @@
|
|
|
21
21
|
function normalizeValue(val, opts) {
|
|
22
22
|
const maxSize = (opts && opts.maxValueBytes) || 4096;
|
|
23
23
|
|
|
24
|
-
// BigInt
|
|
24
|
+
// BigInt keeps string precision
|
|
25
25
|
if (typeof val === 'bigint') {
|
|
26
26
|
return { value: val.toString(10), truncated: false };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Buffer / Uint8Array
|
|
29
|
+
// Treat Buffer / Uint8Array as binary
|
|
30
30
|
if (Buffer.isBuffer(val) || (val instanceof Uint8Array)) {
|
|
31
31
|
return normalizeBuffer(val, maxSize);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Date → ISO
|
|
34
|
+
// Date → ISO string (dateStrings already handles this in the connection
|
|
35
|
+
// options; fallback here)
|
|
35
36
|
if (val instanceof Date) {
|
|
36
37
|
return { value: val.toISOString(), truncated: false };
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
//
|
|
40
|
+
// Objects (including arrays) → JSON-safe
|
|
40
41
|
if (typeof val === 'object' && val !== null) {
|
|
41
42
|
try {
|
|
42
43
|
const json = safeJsonStringify(val);
|
|
@@ -50,19 +51,19 @@ function normalizeValue(val, opts) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
//
|
|
54
|
+
// Primitives pass through
|
|
54
55
|
return { value: val, truncated: false };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
58
|
-
* Buffer/Uint8Array →
|
|
59
|
-
*
|
|
59
|
+
* Buffer/Uint8Array → printable UTF-8 string or base64
|
|
60
|
+
* Truncate and mark when larger than maxValueBytes
|
|
60
61
|
*/
|
|
61
62
|
function normalizeBuffer(buf, maxSize) {
|
|
62
|
-
// Uint8Array → Buffer
|
|
63
|
+
// Uint8Array → Buffer (zero-copy view over the same memory)
|
|
63
64
|
const buffer = Buffer.isBuffer(buf) ? buf : Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
64
65
|
|
|
65
|
-
//
|
|
66
|
+
// Try UTF-8 decoding
|
|
66
67
|
let str;
|
|
67
68
|
let isPrintable = false;
|
|
68
69
|
try {
|
|
@@ -77,7 +78,7 @@ function normalizeBuffer(buf, maxSize) {
|
|
|
77
78
|
|
|
78
79
|
if (byteLength > maxSize) {
|
|
79
80
|
truncated = true;
|
|
80
|
-
//
|
|
81
|
+
// Truncate to maxSize bytes, then decode
|
|
81
82
|
const sliced = buffer.subarray(0, maxSize);
|
|
82
83
|
if (isPrintable) {
|
|
83
84
|
str = sliced.toString('utf8') + '…';
|
|
@@ -85,7 +86,7 @@ function normalizeBuffer(buf, maxSize) {
|
|
|
85
86
|
str = sliced.toString('base64') + '…';
|
|
86
87
|
}
|
|
87
88
|
} else if (isPrintable) {
|
|
88
|
-
str = str; //
|
|
89
|
+
str = str; // already decoded
|
|
89
90
|
} else {
|
|
90
91
|
str = buffer.toString('base64');
|
|
91
92
|
}
|
|
@@ -97,19 +98,19 @@ function normalizeBuffer(buf, maxSize) {
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
/**
|
|
101
|
+
/** Check whether a string is printable UTF-8 (no control characters; common whitespace allowed) */
|
|
101
102
|
function isPrintableUtf8(str) {
|
|
102
103
|
for (let i = 0; i < str.length; i++) {
|
|
103
104
|
const code = str.charCodeAt(i);
|
|
104
105
|
if (code < 0x20 && code !== 0x09 && code !== 0x0A && code !== 0x0D) return false;
|
|
105
106
|
if (code === 0x7F) return false; // DEL
|
|
106
|
-
if (code >= 0xFFFE) return false; //
|
|
107
|
+
if (code >= 0xFFFE) return false; // noncharacter
|
|
107
108
|
}
|
|
108
109
|
return true;
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
/**
|
|
112
|
-
*
|
|
113
|
+
* Safe JSON.stringify that handles circular references and similar pitfalls
|
|
113
114
|
*/
|
|
114
115
|
function safeJsonStringify(val) {
|
|
115
116
|
const seen = new WeakSet();
|
|
@@ -122,22 +123,22 @@ function safeJsonStringify(val) {
|
|
|
122
123
|
});
|
|
123
124
|
}
|
|
124
125
|
|
|
125
|
-
/**
|
|
126
|
+
/** Truncate a string to a byte budget (UTF-8 safe) */
|
|
126
127
|
function truncateString(str, maxBytes) {
|
|
127
128
|
const buf = Buffer.from(str, 'utf8');
|
|
128
129
|
if (buf.length <= maxBytes) return { value: str, truncated: false };
|
|
129
|
-
//
|
|
130
|
+
// Find the last complete UTF-8 character boundary not exceeding maxBytes
|
|
130
131
|
let end = maxBytes;
|
|
131
|
-
while (end > 0 && (buf[end] & 0xC0) === 0x80) end--; //
|
|
132
|
+
while (end > 0 && (buf[end] & 0xC0) === 0x80) end--; // skip multibyte continuation bytes
|
|
132
133
|
return { value: buf.subarray(0, end).toString('utf8') + '…', truncated: true };
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
// ── MySQL
|
|
136
|
+
// ── MySQL table output ──
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
|
-
*
|
|
139
|
+
* Format MySQL query results as a table (in field order)
|
|
139
140
|
* @param {Array} rows
|
|
140
|
-
* @param {Array} fields -
|
|
141
|
+
* @param {Array} fields - field metadata returned by mysql2
|
|
141
142
|
* @param {object} opts - { maxRows, maxValueBytes, truncated }
|
|
142
143
|
*/
|
|
143
144
|
function formatMysqlTable(rows, fields, opts) {
|
|
@@ -145,7 +146,7 @@ function formatMysqlTable(rows, fields, opts) {
|
|
|
145
146
|
const maxValueBytes = (opts && opts.maxValueBytes) || 4096;
|
|
146
147
|
const resultTruncated = opts && opts.truncated;
|
|
147
148
|
|
|
148
|
-
//
|
|
149
|
+
// Normalize row data
|
|
149
150
|
const normalizedRows = [];
|
|
150
151
|
const fieldNames = (fields || []).map(f => f.name);
|
|
151
152
|
|
|
@@ -161,27 +162,27 @@ function formatMysqlTable(rows, fields, opts) {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
if (normalizedRows.length === 0) {
|
|
164
|
-
return '(
|
|
165
|
+
return '(empty result set)';
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
//
|
|
168
|
+
// Compute column widths
|
|
168
169
|
const colWidths = fieldNames.map((name, colIdx) => {
|
|
169
170
|
let w = name.length;
|
|
170
171
|
for (const row of normalizedRows) {
|
|
171
172
|
w = Math.max(w, String(row[colIdx]).length);
|
|
172
173
|
}
|
|
173
|
-
return Math.min(w, 60); //
|
|
174
|
+
return Math.min(w, 60); // cap each column at 60 chars
|
|
174
175
|
});
|
|
175
176
|
|
|
176
|
-
//
|
|
177
|
+
// Build the table
|
|
177
178
|
const sep = colWidths.map(w => '-'.repeat(w + 2)).join('+');
|
|
178
179
|
const lines = [sep];
|
|
179
180
|
|
|
180
|
-
//
|
|
181
|
+
// Header
|
|
181
182
|
lines.push('|' + fieldNames.map((name, i) => ' ' + name.padEnd(colWidths[i]) + ' ').join('|') + '|');
|
|
182
183
|
lines.push(sep);
|
|
183
184
|
|
|
184
|
-
//
|
|
185
|
+
// Data rows
|
|
185
186
|
for (const row of normalizedRows) {
|
|
186
187
|
lines.push('|' + row.map((cell, i) => ' ' + String(cell).padEnd(colWidths[i]) + ' ').join('|') + '|');
|
|
187
188
|
}
|
|
@@ -189,27 +190,27 @@ function formatMysqlTable(rows, fields, opts) {
|
|
|
189
190
|
|
|
190
191
|
let output = lines.join('\n');
|
|
191
192
|
if (resultTruncated) {
|
|
192
|
-
output += `\n⚠
|
|
193
|
+
output += `\n⚠ Results truncated (showing first ${maxRows} rows)`;
|
|
193
194
|
}
|
|
194
195
|
return output;
|
|
195
196
|
}
|
|
196
197
|
|
|
197
|
-
/**
|
|
198
|
+
/** Format a single cell (append truncation marker) */
|
|
198
199
|
function formatCell(norm) {
|
|
199
200
|
if (norm.truncated && norm.byteLength) {
|
|
200
201
|
return String(norm.value) + ` [${norm.byteLength}B]`;
|
|
201
202
|
}
|
|
202
203
|
if (norm.truncated) {
|
|
203
|
-
return String(norm.value) + ' [
|
|
204
|
+
return String(norm.value) + ' [truncated]';
|
|
204
205
|
}
|
|
205
206
|
return norm.value;
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
// ── Redis
|
|
209
|
+
// ── Redis table output ──
|
|
209
210
|
|
|
210
211
|
/**
|
|
211
|
-
*
|
|
212
|
-
* @param {*} result - Redis
|
|
212
|
+
* Format a Redis result as a readable table
|
|
213
|
+
* @param {*} result - raw data returned by Redis
|
|
213
214
|
* @param {object} info - { shape, command, maxItems, maxValueBytes }
|
|
214
215
|
* @returns {string}
|
|
215
216
|
*/
|
|
@@ -222,36 +223,36 @@ function formatRedisTable(result, info) {
|
|
|
222
223
|
if (result === null) return '(nil)';
|
|
223
224
|
if (typeof result === 'number') return String(result);
|
|
224
225
|
const norm = normalizeValue(result, { maxValueBytes: info && info.maxValueBytes });
|
|
225
|
-
return String(norm.value) + (norm.truncated ? ' [
|
|
226
|
+
return String(norm.value) + (norm.truncated ? ' [truncated]' : '');
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
// list
|
|
229
230
|
if (shape === 'list') {
|
|
230
231
|
const items = Array.isArray(result) ? result : [result];
|
|
231
|
-
if (items.length === 0) return '(
|
|
232
|
+
if (items.length === 0) return '(empty list)';
|
|
232
233
|
const lines = [];
|
|
233
234
|
const count = Math.min(items.length, maxItems);
|
|
234
235
|
for (let i = 0; i < count; i++) {
|
|
235
236
|
const norm = normalizeValue(items[i], { maxValueBytes: info && info.maxValueBytes });
|
|
236
|
-
lines.push(`${i + 1}) ${String(norm.value)}${norm.truncated ? ' [
|
|
237
|
+
lines.push(`${i + 1}) ${String(norm.value)}${norm.truncated ? ' [truncated]' : ''}`);
|
|
237
238
|
}
|
|
238
|
-
if (items.length > maxItems) lines.push(`⚠
|
|
239
|
+
if (items.length > maxItems) lines.push(`⚠ Showing first ${maxItems} of ${items.length} item(s)`);
|
|
239
240
|
return lines.join('\n');
|
|
240
241
|
}
|
|
241
242
|
|
|
242
|
-
// pairs (HGETALL
|
|
243
|
+
// pairs (HGETALL etc.)
|
|
243
244
|
if (shape === 'pairs') {
|
|
244
245
|
const arr = Array.isArray(result) ? result : [];
|
|
245
|
-
if (arr.length === 0) return '(
|
|
246
|
+
if (arr.length === 0) return '(empty map)';
|
|
246
247
|
const lines = [];
|
|
247
248
|
const pairCount = Math.min(Math.floor(arr.length / 2), maxItems);
|
|
248
249
|
for (let i = 0; i < pairCount; i++) {
|
|
249
250
|
const field = String(arr[i * 2] || '');
|
|
250
251
|
const val = normalizeValue(arr[i * 2 + 1], { maxValueBytes: info && info.maxValueBytes });
|
|
251
|
-
lines.push(`${field}: ${String(val.value)}${val.truncated ? ' [
|
|
252
|
+
lines.push(`${field}: ${String(val.value)}${val.truncated ? ' [truncated]' : ''}`);
|
|
252
253
|
}
|
|
253
254
|
if (Math.floor(arr.length / 2) > maxItems) {
|
|
254
|
-
lines.push(`⚠
|
|
255
|
+
lines.push(`⚠ Showing first ${maxItems} field(s)`);
|
|
255
256
|
}
|
|
256
257
|
return lines.join('\n');
|
|
257
258
|
}
|
|
@@ -266,7 +267,7 @@ function formatRedisTable(result, info) {
|
|
|
266
267
|
for (let i = 0; i < count; i++) {
|
|
267
268
|
lines.push(`${i + 1}) ${String(items[i])}`);
|
|
268
269
|
}
|
|
269
|
-
if (items.length > maxItems) lines.push(`⚠
|
|
270
|
+
if (items.length > maxItems) lines.push(`⚠ Showing first ${maxItems} item(s)`);
|
|
270
271
|
return lines.join('\n');
|
|
271
272
|
}
|
|
272
273
|
|
|
@@ -282,10 +283,10 @@ function formatRedisTable(result, info) {
|
|
|
282
283
|
const count = Math.min(clients.length, maxItems);
|
|
283
284
|
const lines = [];
|
|
284
285
|
for (let i = 0; i < count; i++) {
|
|
285
|
-
lines.push(`---
|
|
286
|
+
lines.push(`--- Client ${i + 1} ---`);
|
|
286
287
|
lines.push(clients[i]);
|
|
287
288
|
}
|
|
288
|
-
if (clients.length > maxItems) lines.push(`⚠
|
|
289
|
+
if (clients.length > maxItems) lines.push(`⚠ Showing first ${maxItems} client(s)`);
|
|
289
290
|
return lines.join('\n');
|
|
290
291
|
}
|
|
291
292
|
return String(result);
|
|
@@ -297,16 +298,16 @@ function formatRedisTable(result, info) {
|
|
|
297
298
|
// ── JSON envelope ──
|
|
298
299
|
|
|
299
300
|
/**
|
|
300
|
-
*
|
|
301
|
+
* Build the JSON output envelope
|
|
301
302
|
* @param {object} params - { source, target, elapsedMs, count, truncated, result }
|
|
302
303
|
* @returns {string}
|
|
303
304
|
*/
|
|
304
305
|
function formatJson(params) {
|
|
305
306
|
const envelope = {
|
|
306
307
|
source: params.source, // 'mysql' | 'redis'
|
|
307
|
-
target: params.target, //
|
|
308
|
+
target: params.target, // sanitized target
|
|
308
309
|
elapsedMs: params.elapsedMs,
|
|
309
|
-
count: params.count, //
|
|
310
|
+
count: params.count, // row/item count
|
|
310
311
|
truncated: params.truncated,
|
|
311
312
|
result: params.result,
|
|
312
313
|
};
|