@carllee1983/dbcli 1.39.1 → 1.41.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/dbcli.mdc +38 -14
- package/.cursor/skills/dbcli/reference.md +307 -8
- package/.cursor-plugin/plugin.json +2 -1
- package/.github/skills/dbcli/SKILL.md +38 -14
- package/.github/skills/dbcli/reference.md +307 -8
- package/CHANGELOG.md +64 -1
- package/README.zh-TW.md +1 -1
- package/assets/SKILL.md +38 -14
- package/assets/SKILL.zh-TW.md +29 -9
- package/assets/reference.md +307 -8
- package/assets/tasks/diagnose-slow-query.md +8 -0
- package/assets/tasks/mongo-safe-backfill.md +56 -0
- package/assets/tasks/mongo-schema-drift-review.md +51 -0
- package/assets/tasks/orm-drift-review.md +55 -0
- package/dist/cli.mjs +10793 -6509
- package/dist/core.d.ts +3 -0
- package/dist/core.mjs +223 -64
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/INSTALL.md +19 -10
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +38 -14
- package/plugins/dbcli-agent/skills/dbcli/reference.md +307 -8
- package/skills/dbcli/SKILL.md +38 -14
- package/skills/dbcli/reference.md +307 -8
package/dist/core.d.ts
CHANGED
|
@@ -86,6 +86,8 @@ export interface ColumnSchema {
|
|
|
86
86
|
export interface TableSchema {
|
|
87
87
|
/** Table name */
|
|
88
88
|
name: string;
|
|
89
|
+
/** Exact database schema/catalog namespace, when reliably available */
|
|
90
|
+
schema?: string;
|
|
89
91
|
/** Array of columns in the table */
|
|
90
92
|
columns: ColumnSchema[];
|
|
91
93
|
/** Approximate row count (if available) */
|
|
@@ -98,6 +100,7 @@ export interface TableSchema {
|
|
|
98
100
|
foreignKeys?: Array<{
|
|
99
101
|
name: string;
|
|
100
102
|
columns: string[];
|
|
103
|
+
refSchema?: string;
|
|
101
104
|
refTable: string;
|
|
102
105
|
refColumns: string[];
|
|
103
106
|
}>;
|
package/dist/core.mjs
CHANGED
|
@@ -13434,6 +13434,7 @@ var COMMAND_CAPABILITY_KEYS = Object.freeze([
|
|
|
13434
13434
|
"schemaSingle",
|
|
13435
13435
|
"schemaFullScan",
|
|
13436
13436
|
"query",
|
|
13437
|
+
"lint",
|
|
13437
13438
|
"queryOutput",
|
|
13438
13439
|
"queryLimitGuard",
|
|
13439
13440
|
"q",
|
|
@@ -13482,6 +13483,7 @@ var SQL_BASE = {
|
|
|
13482
13483
|
schemaSingle: cap("supported", "readonly", "Reads a single table schema."),
|
|
13483
13484
|
schemaFullScan: cap("supported", "readonly", "Full scan, refresh, and reset are supported."),
|
|
13484
13485
|
query: cap("supported", "readonly", "Runs SQL through permission and blacklist guards."),
|
|
13486
|
+
lint: cap("supported", "readonly", "Statically analyzes SQL without connecting to the database."),
|
|
13485
13487
|
queryOutput: cap("supported", "readonly", "table/json/csv output is supported."),
|
|
13486
13488
|
queryLimitGuard: cap("supported", "readonly", "Query-only auto-limit and size guard are supported."),
|
|
13487
13489
|
q: cap("supported", "readonly", "Saved SQL snippets allow SELECT/WITH only."),
|
|
@@ -13511,6 +13513,7 @@ var ENGINE_CAPABILITIES = Object.freeze({
|
|
|
13511
13513
|
mariadb: Object.freeze(SQL_BASE),
|
|
13512
13514
|
mongodb: Object.freeze({
|
|
13513
13515
|
...SQL_BASE,
|
|
13516
|
+
lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
|
|
13514
13517
|
schemaSingle: cap("limited", "readonly", "MongoDB schema is sampled from documents."),
|
|
13515
13518
|
schemaFullScan: cap("limited", "readonly", "Full scan is sampled and document-oriented."),
|
|
13516
13519
|
query: cap("limited", "readonly", "Uses JSON filter or aggregation syntax, not SQL."),
|
|
@@ -13529,6 +13532,7 @@ var ENGINE_CAPABILITIES = Object.freeze({
|
|
|
13529
13532
|
}),
|
|
13530
13533
|
redis: Object.freeze({
|
|
13531
13534
|
...SQL_BASE,
|
|
13535
|
+
lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
|
|
13532
13536
|
schemaSingle: cap("limited", "readonly", "Per-key synthetic schema only."),
|
|
13533
13537
|
schemaFullScan: cap("unsupported", "none", "Redis has no full schema cache scan."),
|
|
13534
13538
|
query: cap("limited", "readonly", "Runs allow-listed Redis commands."),
|
|
@@ -13547,6 +13551,7 @@ var ENGINE_CAPABILITIES = Object.freeze({
|
|
|
13547
13551
|
}),
|
|
13548
13552
|
elasticsearch: Object.freeze({
|
|
13549
13553
|
...SQL_BASE,
|
|
13554
|
+
lint: cap("unsupported", "none", "Static lint accepts SQL connections only."),
|
|
13550
13555
|
schemaSingle: cap("limited", "readonly", "Schema flattens index mappings."),
|
|
13551
13556
|
schemaFullScan: cap("supported", "readonly", "Full scan iterates non-system indices."),
|
|
13552
13557
|
query: cap("limited", "readonly", "Uses JSON DSL or Lucene query strings with an index."),
|
|
@@ -14437,6 +14442,10 @@ function fixDoubleEncodedUtf8(str) {
|
|
|
14437
14442
|
}
|
|
14438
14443
|
|
|
14439
14444
|
// src/adapters/postgresql-adapter.ts
|
|
14445
|
+
function quoteIdentifier(identifier) {
|
|
14446
|
+
return `"${identifier.replaceAll('"', '""')}"`;
|
|
14447
|
+
}
|
|
14448
|
+
|
|
14440
14449
|
class PostgreSQLAdapter {
|
|
14441
14450
|
pool = null;
|
|
14442
14451
|
client = null;
|
|
@@ -14522,6 +14531,7 @@ class PostgreSQLAdapter {
|
|
|
14522
14531
|
try {
|
|
14523
14532
|
const query = `
|
|
14524
14533
|
SELECT
|
|
14534
|
+
n.nspname as schema_name,
|
|
14525
14535
|
c.relname as table_name,
|
|
14526
14536
|
c.reltuples::bigint as estimated_rows,
|
|
14527
14537
|
CASE c.relkind
|
|
@@ -14541,6 +14551,7 @@ class PostgreSQLAdapter {
|
|
|
14541
14551
|
const result = await this.execute(query);
|
|
14542
14552
|
return result.rows.map((row) => ({
|
|
14543
14553
|
name: row.table_name,
|
|
14554
|
+
schema: row.schema_name,
|
|
14544
14555
|
columns: [],
|
|
14545
14556
|
columnCount: row.column_count,
|
|
14546
14557
|
rowCount: Math.max(0, row.estimated_rows || 0),
|
|
@@ -14569,9 +14580,15 @@ class PostgreSQLAdapter {
|
|
|
14569
14580
|
SELECT EXISTS(
|
|
14570
14581
|
SELECT 1 FROM information_schema.table_constraints tc
|
|
14571
14582
|
JOIN information_schema.key_column_usage kcu
|
|
14572
|
-
ON tc.
|
|
14573
|
-
|
|
14574
|
-
AND tc.
|
|
14583
|
+
ON tc.constraint_catalog = kcu.constraint_catalog
|
|
14584
|
+
AND tc.constraint_schema = kcu.constraint_schema
|
|
14585
|
+
AND tc.constraint_name = kcu.constraint_name
|
|
14586
|
+
WHERE tc.table_catalog = c.table_catalog
|
|
14587
|
+
AND tc.table_schema = c.table_schema
|
|
14588
|
+
AND tc.table_name = c.table_name
|
|
14589
|
+
AND kcu.table_catalog = c.table_catalog
|
|
14590
|
+
AND kcu.table_schema = c.table_schema
|
|
14591
|
+
AND kcu.table_name = c.table_name
|
|
14575
14592
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
14576
14593
|
AND kcu.column_name = c.column_name
|
|
14577
14594
|
)
|
|
@@ -14594,8 +14611,12 @@ class PostgreSQLAdapter {
|
|
|
14594
14611
|
c.column_name as name,
|
|
14595
14612
|
array_agg(e.enumlabel ORDER BY e.enumsortorder) as enum_values
|
|
14596
14613
|
FROM information_schema.columns c
|
|
14597
|
-
JOIN pg_type
|
|
14598
|
-
|
|
14614
|
+
JOIN pg_catalog.pg_type AS enum_type
|
|
14615
|
+
ON enum_type.typname = c.udt_name
|
|
14616
|
+
JOIN pg_catalog.pg_namespace AS enum_schema
|
|
14617
|
+
ON enum_schema.oid = enum_type.typnamespace
|
|
14618
|
+
AND enum_schema.nspname = c.udt_schema
|
|
14619
|
+
JOIN pg_catalog.pg_enum AS e ON e.enumtypid = enum_type.oid
|
|
14599
14620
|
WHERE c.table_name = $1
|
|
14600
14621
|
AND c.table_schema = 'public'
|
|
14601
14622
|
GROUP BY c.column_name
|
|
@@ -14608,17 +14629,40 @@ class PostgreSQLAdapter {
|
|
|
14608
14629
|
}
|
|
14609
14630
|
const fkQuery = `
|
|
14610
14631
|
SELECT
|
|
14611
|
-
|
|
14612
|
-
array_agg(
|
|
14613
|
-
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14632
|
+
constraint_info.conname as name,
|
|
14633
|
+
array_agg(source_column.attname ORDER BY source_key.ordinality) as columns,
|
|
14634
|
+
referenced_schema.nspname as ref_schema,
|
|
14635
|
+
referenced_table.relname as ref_table,
|
|
14636
|
+
array_agg(referenced_column.attname ORDER BY source_key.ordinality) as ref_columns
|
|
14637
|
+
FROM pg_catalog.pg_constraint AS constraint_info
|
|
14638
|
+
JOIN pg_catalog.pg_class AS source_table
|
|
14639
|
+
ON source_table.oid = constraint_info.conrelid
|
|
14640
|
+
JOIN pg_catalog.pg_namespace AS source_schema
|
|
14641
|
+
ON source_schema.oid = source_table.relnamespace
|
|
14642
|
+
JOIN pg_catalog.pg_class AS referenced_table
|
|
14643
|
+
ON referenced_table.oid = constraint_info.confrelid
|
|
14644
|
+
JOIN pg_catalog.pg_namespace AS referenced_schema
|
|
14645
|
+
ON referenced_schema.oid = referenced_table.relnamespace
|
|
14646
|
+
JOIN LATERAL unnest(constraint_info.conkey) WITH ORDINALITY
|
|
14647
|
+
AS source_key(attnum, ordinality) ON TRUE
|
|
14648
|
+
JOIN LATERAL unnest(constraint_info.confkey) WITH ORDINALITY
|
|
14649
|
+
AS referenced_key(attnum, ordinality)
|
|
14650
|
+
ON referenced_key.ordinality = source_key.ordinality
|
|
14651
|
+
JOIN pg_catalog.pg_attribute AS source_column
|
|
14652
|
+
ON source_column.attrelid = constraint_info.conrelid
|
|
14653
|
+
AND source_column.attnum = source_key.attnum
|
|
14654
|
+
JOIN pg_catalog.pg_attribute AS referenced_column
|
|
14655
|
+
ON referenced_column.attrelid = constraint_info.confrelid
|
|
14656
|
+
AND referenced_column.attnum = referenced_key.attnum
|
|
14657
|
+
WHERE constraint_info.contype = 'f'
|
|
14658
|
+
AND source_table.relname = $1
|
|
14659
|
+
AND source_schema.nspname = 'public'
|
|
14660
|
+
GROUP BY
|
|
14661
|
+
constraint_info.oid,
|
|
14662
|
+
constraint_info.conname,
|
|
14663
|
+
referenced_schema.nspname,
|
|
14664
|
+
referenced_table.relname
|
|
14665
|
+
ORDER BY constraint_info.oid
|
|
14622
14666
|
`;
|
|
14623
14667
|
const fkResult = await this.execute(fkQuery, [tableName]);
|
|
14624
14668
|
const fkResults = fkResult.rows;
|
|
@@ -14646,36 +14690,49 @@ class PostgreSQLAdapter {
|
|
|
14646
14690
|
const indexResult = await this.execute(indexQuery, [tableName]);
|
|
14647
14691
|
const indexResults = indexResult.rows;
|
|
14648
14692
|
const estimateQuery = `
|
|
14649
|
-
SELECT reltuples::bigint as estimated_rows
|
|
14650
|
-
FROM pg_class
|
|
14651
|
-
|
|
14693
|
+
SELECT source_table.reltuples::bigint as estimated_rows
|
|
14694
|
+
FROM pg_catalog.pg_class AS source_table
|
|
14695
|
+
JOIN pg_catalog.pg_namespace AS source_schema
|
|
14696
|
+
ON source_schema.oid = source_table.relnamespace
|
|
14697
|
+
WHERE source_table.relname = $1
|
|
14698
|
+
AND source_schema.nspname = 'public'
|
|
14652
14699
|
`;
|
|
14653
14700
|
const estimateResult = await this.execute(estimateQuery, [
|
|
14654
14701
|
tableName
|
|
14655
14702
|
]);
|
|
14656
14703
|
const estimateResults = estimateResult.rows;
|
|
14657
14704
|
const pkQuery = `
|
|
14658
|
-
SELECT
|
|
14659
|
-
|
|
14660
|
-
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
)
|
|
14705
|
+
SELECT
|
|
14706
|
+
array_agg(primary_key_column.attname ORDER BY primary_key.ordinality) as columns
|
|
14707
|
+
FROM pg_catalog.pg_index AS index_info
|
|
14708
|
+
JOIN pg_catalog.pg_class AS source_table
|
|
14709
|
+
ON source_table.oid = index_info.indrelid
|
|
14710
|
+
JOIN pg_catalog.pg_namespace AS source_schema
|
|
14711
|
+
ON source_schema.oid = source_table.relnamespace
|
|
14712
|
+
JOIN LATERAL unnest(index_info.indkey) WITH ORDINALITY
|
|
14713
|
+
AS primary_key(attnum, ordinality) ON TRUE
|
|
14714
|
+
JOIN pg_catalog.pg_attribute AS primary_key_column
|
|
14715
|
+
ON primary_key_column.attrelid = source_table.oid
|
|
14716
|
+
AND primary_key_column.attnum = primary_key.attnum
|
|
14717
|
+
WHERE index_info.indisprimary
|
|
14718
|
+
AND source_table.relname = $1
|
|
14719
|
+
AND source_schema.nspname = 'public'
|
|
14666
14720
|
`;
|
|
14667
14721
|
const pkResult = await this.execute(pkQuery, [tableName]);
|
|
14668
14722
|
const pkResults = pkResult.rows;
|
|
14669
|
-
const
|
|
14723
|
+
const qualifiedTableName = `${quoteIdentifier("public")}.${quoteIdentifier(tableName)}`;
|
|
14724
|
+
const countResult = await this.execute(`SELECT COUNT(*) as count FROM ${qualifiedTableName}`);
|
|
14670
14725
|
const primaryKeyArray = Array.isArray(pkResults[0]?.columns) ? pkResults[0].columns : [];
|
|
14671
14726
|
const safeForeignKeys = fkResults.map((fk) => ({
|
|
14672
14727
|
name: fk.name,
|
|
14673
14728
|
columns: Array.isArray(fk.columns) ? fk.columns : [],
|
|
14729
|
+
refSchema: fk.ref_schema,
|
|
14674
14730
|
refTable: fk.ref_table,
|
|
14675
14731
|
refColumns: Array.isArray(fk.ref_columns) ? fk.ref_columns : []
|
|
14676
14732
|
}));
|
|
14677
14733
|
const schema = {
|
|
14678
14734
|
name: tableName,
|
|
14735
|
+
schema: "public",
|
|
14679
14736
|
columns: columns.map((col) => ({
|
|
14680
14737
|
name: col.name,
|
|
14681
14738
|
type: col.type,
|
|
@@ -17423,6 +17480,7 @@ class SessionIdService {
|
|
|
17423
17480
|
|
|
17424
17481
|
// src/core/config-binding.ts
|
|
17425
17482
|
import { createHash } from "crypto";
|
|
17483
|
+
import { mkdir as mkdir4, unlink } from "fs/promises";
|
|
17426
17484
|
import { homedir } from "os";
|
|
17427
17485
|
import { basename, join as join3, resolve } from "path";
|
|
17428
17486
|
var BINDING_FILE_NAME = "config.json";
|
|
@@ -17467,8 +17525,8 @@ async function writeProjectBinding(projectPath, storagePath = getProjectStorageP
|
|
|
17467
17525
|
createdAt: new Date().toISOString()
|
|
17468
17526
|
}
|
|
17469
17527
|
};
|
|
17470
|
-
await
|
|
17471
|
-
await
|
|
17528
|
+
await mkdir4(projectPath, { recursive: true });
|
|
17529
|
+
await mkdir4(storagePath, { recursive: true });
|
|
17472
17530
|
await Bun.file(join3(projectPath, BINDING_FILE_NAME)).write(JSON.stringify(binding, null, 2));
|
|
17473
17531
|
return binding;
|
|
17474
17532
|
}
|
|
@@ -21624,6 +21682,7 @@ async function loadEnvFile(filePath) {
|
|
|
21624
21682
|
|
|
21625
21683
|
// src/core/config-v2.ts
|
|
21626
21684
|
import { join as join4 } from "path";
|
|
21685
|
+
import { mkdir as mkdir5, rename as rename3 } from "fs/promises";
|
|
21627
21686
|
function detectConfigVersion(raw) {
|
|
21628
21687
|
if (typeof raw === "object" && raw !== null && "version" in raw && raw.version === 2 && "connections" in raw) {
|
|
21629
21688
|
return 2;
|
|
@@ -21667,10 +21726,10 @@ async function writeV2Config(path, config) {
|
|
|
21667
21726
|
const storagePath = await resolveConfigStoragePath(path);
|
|
21668
21727
|
const configPath = join4(storagePath, "config.json");
|
|
21669
21728
|
const tmpPath = `${configPath}.tmp`;
|
|
21670
|
-
await
|
|
21729
|
+
await mkdir5(storagePath, { recursive: true });
|
|
21671
21730
|
const json = JSON.stringify(config, null, 2);
|
|
21672
21731
|
await Bun.write(tmpPath, json);
|
|
21673
|
-
await
|
|
21732
|
+
await rename3(tmpPath, configPath);
|
|
21674
21733
|
}
|
|
21675
21734
|
function listConnections(config) {
|
|
21676
21735
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
@@ -21689,6 +21748,7 @@ function listConnections(config) {
|
|
|
21689
21748
|
|
|
21690
21749
|
// src/core/config.ts
|
|
21691
21750
|
import { join as join9 } from "path";
|
|
21751
|
+
import { mkdir as mkdir6 } from "fs/promises";
|
|
21692
21752
|
var _globalConnectionName;
|
|
21693
21753
|
function getGlobalConnectionName() {
|
|
21694
21754
|
return _globalConnectionName;
|
|
@@ -21754,7 +21814,7 @@ function parseEnvPassword(content) {
|
|
|
21754
21814
|
return match?.[1] != null ? match[1].trim() : null;
|
|
21755
21815
|
}
|
|
21756
21816
|
var configModule = {
|
|
21757
|
-
async read(path, connectionName) {
|
|
21817
|
+
async read(path, connectionName, options = {}) {
|
|
21758
21818
|
const effectiveConnectionName = connectionName ?? _globalConnectionName;
|
|
21759
21819
|
try {
|
|
21760
21820
|
const binding = await readProjectBinding(path);
|
|
@@ -21798,23 +21858,27 @@ var configModule = {
|
|
|
21798
21858
|
resolvedConnection.password = legacyPassword;
|
|
21799
21859
|
}
|
|
21800
21860
|
let schema = (v2Config.schemas ?? {})[resolved.name] ?? v2Config.schema;
|
|
21801
|
-
|
|
21802
|
-
|
|
21803
|
-
|
|
21804
|
-
|
|
21805
|
-
|
|
21806
|
-
|
|
21807
|
-
|
|
21808
|
-
|
|
21809
|
-
|
|
21810
|
-
|
|
21811
|
-
|
|
21812
|
-
|
|
21813
|
-
|
|
21861
|
+
if (options.loadLayeredSchema !== false) {
|
|
21862
|
+
try {
|
|
21863
|
+
const { SchemaLayeredLoader: SchemaLayeredLoader2 } = await Promise.resolve().then(() => (init_schema_loader(), exports_schema_loader));
|
|
21864
|
+
const loader = new SchemaLayeredLoader2(storagePath, {
|
|
21865
|
+
connectionName: resolved.name
|
|
21866
|
+
});
|
|
21867
|
+
const { cache, index } = await loader.initialize();
|
|
21868
|
+
if (index && Object.keys(index.tables).length > 0) {
|
|
21869
|
+
const layeredSchema = {};
|
|
21870
|
+
for (const tableName of Object.keys(index.tables)) {
|
|
21871
|
+
const s = await cache.getTableSchema(tableName);
|
|
21872
|
+
if (s)
|
|
21873
|
+
layeredSchema[tableName] = s;
|
|
21874
|
+
}
|
|
21875
|
+
if (Object.keys(layeredSchema).length > 0) {
|
|
21876
|
+
schema = layeredSchema;
|
|
21877
|
+
}
|
|
21814
21878
|
}
|
|
21879
|
+
} catch {
|
|
21880
|
+
console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
|
|
21815
21881
|
}
|
|
21816
|
-
} catch {
|
|
21817
|
-
console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
|
|
21818
21882
|
}
|
|
21819
21883
|
return DbcliConfigSchema.parse({
|
|
21820
21884
|
connection: resolvedConnection,
|
|
@@ -21898,7 +21962,7 @@ var configModule = {
|
|
|
21898
21962
|
isDirectory = false;
|
|
21899
21963
|
}
|
|
21900
21964
|
if (isDirectory || path.endsWith(".dbcli") || path === storagePath && isDirectory) {
|
|
21901
|
-
await
|
|
21965
|
+
await mkdir6(storagePath, { recursive: true });
|
|
21902
21966
|
const hasEnvReferences = isEnvReference(config.connection.password);
|
|
21903
21967
|
if (hasEnvReferences) {
|
|
21904
21968
|
const configPath = join9(storagePath, "config.json");
|
|
@@ -21940,7 +22004,7 @@ DBCLI_PASSWORD=${password}
|
|
|
21940
22004
|
};
|
|
21941
22005
|
|
|
21942
22006
|
// src/utils/redaction.ts
|
|
21943
|
-
var SQL_SUBCOMMANDS = new Set(["query", "export"]);
|
|
22007
|
+
var SQL_SUBCOMMANDS = new Set(["query", "export", "lint"]);
|
|
21944
22008
|
var REDACTED_VALUE_FLAGS = new Set([
|
|
21945
22009
|
"--where",
|
|
21946
22010
|
"--set",
|
|
@@ -21951,21 +22015,101 @@ var REDACTED_VALUE_FLAGS = new Set([
|
|
|
21951
22015
|
"--use",
|
|
21952
22016
|
"--password",
|
|
21953
22017
|
"--token",
|
|
21954
|
-
"--secret"
|
|
22018
|
+
"--secret",
|
|
22019
|
+
"--bulk"
|
|
22020
|
+
]);
|
|
22021
|
+
var KEEP_VALUE_FLAGS = new Set([
|
|
22022
|
+
"--format",
|
|
22023
|
+
"--conn-name",
|
|
22024
|
+
"--min-severity",
|
|
22025
|
+
"--output",
|
|
22026
|
+
"--limit",
|
|
22027
|
+
"--collection",
|
|
22028
|
+
"--index"
|
|
21955
22029
|
]);
|
|
21956
|
-
var
|
|
22030
|
+
var LINT_BOOLEAN_FLAGS = new Set(["--no-schema", "--recovery"]);
|
|
22031
|
+
function optionParts(token) {
|
|
22032
|
+
const equals = token.indexOf("=");
|
|
22033
|
+
return equals === -1 ? { name: token, inlineValue: undefined } : { name: token.slice(0, equals), inlineValue: token.slice(equals + 1) };
|
|
22034
|
+
}
|
|
22035
|
+
function findSensitiveSubcommand(argv) {
|
|
22036
|
+
for (let index = 1;index < argv.length; index++) {
|
|
22037
|
+
const token = argv[index];
|
|
22038
|
+
if (token.startsWith("--")) {
|
|
22039
|
+
const { name, inlineValue } = optionParts(token);
|
|
22040
|
+
if (inlineValue === undefined && (REDACTED_VALUE_FLAGS.has(name) || KEEP_VALUE_FLAGS.has(name))) {
|
|
22041
|
+
index++;
|
|
22042
|
+
}
|
|
22043
|
+
continue;
|
|
22044
|
+
}
|
|
22045
|
+
if (SQL_SUBCOMMANDS.has(token))
|
|
22046
|
+
return { index, command: token };
|
|
22047
|
+
}
|
|
22048
|
+
return null;
|
|
22049
|
+
}
|
|
22050
|
+
function sensitiveArgvValues(argv) {
|
|
22051
|
+
const sensitiveCommand = findSensitiveSubcommand(argv);
|
|
22052
|
+
const values = new Set;
|
|
22053
|
+
let capturedSingleSql = false;
|
|
22054
|
+
let afterEndOfOptions = false;
|
|
22055
|
+
for (let index = 0;index < argv.length; index++) {
|
|
22056
|
+
const token = argv[index];
|
|
22057
|
+
if (!afterEndOfOptions && token === "--") {
|
|
22058
|
+
afterEndOfOptions = true;
|
|
22059
|
+
continue;
|
|
22060
|
+
}
|
|
22061
|
+
if (!afterEndOfOptions && token.startsWith("--")) {
|
|
22062
|
+
const { name, inlineValue } = optionParts(token);
|
|
22063
|
+
if (REDACTED_VALUE_FLAGS.has(name)) {
|
|
22064
|
+
const value = inlineValue ?? argv[index + 1];
|
|
22065
|
+
if (value) {
|
|
22066
|
+
values.add(value);
|
|
22067
|
+
if (name === "--bulk") {
|
|
22068
|
+
for (const item of value.split(",")) {
|
|
22069
|
+
values.add(item);
|
|
22070
|
+
if (item.startsWith("@") && item.length > 1) {
|
|
22071
|
+
values.add(item.slice(1));
|
|
22072
|
+
}
|
|
22073
|
+
}
|
|
22074
|
+
}
|
|
22075
|
+
}
|
|
22076
|
+
if (inlineValue === undefined)
|
|
22077
|
+
index++;
|
|
22078
|
+
} else if (inlineValue === undefined && KEEP_VALUE_FLAGS.has(name)) {
|
|
22079
|
+
index++;
|
|
22080
|
+
} else if (sensitiveCommand?.command === "lint" && LINT_BOOLEAN_FLAGS.has(name)) {
|
|
22081
|
+
continue;
|
|
22082
|
+
} else if (!sensitiveCommand || index < sensitiveCommand.index) {
|
|
22083
|
+
continue;
|
|
22084
|
+
} else {
|
|
22085
|
+
values.add(token);
|
|
22086
|
+
capturedSingleSql = true;
|
|
22087
|
+
}
|
|
22088
|
+
continue;
|
|
22089
|
+
}
|
|
22090
|
+
if (sensitiveCommand && index > sensitiveCommand.index && (sensitiveCommand.command === "lint" || !capturedSingleSql)) {
|
|
22091
|
+
values.add(token);
|
|
22092
|
+
capturedSingleSql = true;
|
|
22093
|
+
}
|
|
22094
|
+
}
|
|
22095
|
+
return Array.from(values).sort((left, right) => right.length - left.length);
|
|
22096
|
+
}
|
|
21957
22097
|
function redactArgv(argv) {
|
|
21958
22098
|
if (argv.length === 0)
|
|
21959
22099
|
return "<unknown>";
|
|
21960
|
-
const
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
21965
|
-
for (let i = 2;i < argv.length; i++) {
|
|
22100
|
+
const sensitiveCommand = findSensitiveSubcommand(argv);
|
|
22101
|
+
const out = [];
|
|
22102
|
+
let redactedSingleSql = false;
|
|
22103
|
+
let afterEndOfOptions = false;
|
|
22104
|
+
for (let i = 0;i < argv.length; i++) {
|
|
21966
22105
|
const tok = argv[i];
|
|
21967
|
-
if (tok
|
|
21968
|
-
|
|
22106
|
+
if (!afterEndOfOptions && tok === "--") {
|
|
22107
|
+
out.push(tok);
|
|
22108
|
+
afterEndOfOptions = true;
|
|
22109
|
+
continue;
|
|
22110
|
+
}
|
|
22111
|
+
if (!afterEndOfOptions && tok.startsWith("--")) {
|
|
22112
|
+
const { name, inlineValue } = optionParts(tok);
|
|
21969
22113
|
if (REDACTED_VALUE_FLAGS.has(name)) {
|
|
21970
22114
|
out.push(`${name} <redacted>`);
|
|
21971
22115
|
if (inlineValue === undefined)
|
|
@@ -21977,23 +22121,37 @@ function redactArgv(argv) {
|
|
|
21977
22121
|
out.push(tok);
|
|
21978
22122
|
} else {
|
|
21979
22123
|
out.push(name);
|
|
21980
|
-
if (i + 1 < argv.length
|
|
22124
|
+
if (i + 1 < argv.length) {
|
|
21981
22125
|
out.push(argv[++i]);
|
|
21982
22126
|
}
|
|
21983
22127
|
}
|
|
21984
22128
|
continue;
|
|
21985
22129
|
}
|
|
21986
|
-
|
|
21987
|
-
|
|
22130
|
+
if (sensitiveCommand?.command === "lint" && LINT_BOOLEAN_FLAGS.has(name)) {
|
|
22131
|
+
out.push(tok);
|
|
22132
|
+
continue;
|
|
22133
|
+
}
|
|
22134
|
+
if (!sensitiveCommand || i < sensitiveCommand.index) {
|
|
22135
|
+
out.push(tok);
|
|
22136
|
+
continue;
|
|
22137
|
+
}
|
|
21988
22138
|
}
|
|
21989
|
-
if (i
|
|
22139
|
+
if (sensitiveCommand && i > sensitiveCommand.index && (sensitiveCommand.command === "lint" || !redactedSingleSql)) {
|
|
21990
22140
|
out.push("<sql>");
|
|
22141
|
+
redactedSingleSql = true;
|
|
21991
22142
|
continue;
|
|
21992
22143
|
}
|
|
21993
22144
|
out.push(tok);
|
|
21994
22145
|
}
|
|
21995
22146
|
return out.join(" ");
|
|
21996
22147
|
}
|
|
22148
|
+
function redactArgvSensitiveText(text, argv) {
|
|
22149
|
+
let redacted = text;
|
|
22150
|
+
for (const value of sensitiveArgvValues(argv)) {
|
|
22151
|
+
redacted = redacted.split(value).join("<redacted>");
|
|
22152
|
+
}
|
|
22153
|
+
return redacted;
|
|
22154
|
+
}
|
|
21997
22155
|
function redactSensitive(text) {
|
|
21998
22156
|
return text.replace(/\b(password|token|apiKey|secret|key|token|auth|credential|pass|pwd|sid)([:=]|\s+)([^\s"';,]+)/gi, "$1$2<redacted>");
|
|
21999
22157
|
}
|
|
@@ -22039,6 +22197,7 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
22039
22197
|
if (outcome.error) {
|
|
22040
22198
|
errorMessage = outcome.error instanceof Error ? outcome.error.message : String(outcome.error);
|
|
22041
22199
|
errorMessage = redactSensitive(errorMessage);
|
|
22200
|
+
errorMessage = redactArgvSensitiveText(errorMessage, process.argv);
|
|
22042
22201
|
}
|
|
22043
22202
|
const entry = {
|
|
22044
22203
|
engine,
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -78,8 +78,13 @@ used by Gemini/Antigravity-style agents.
|
|
|
78
78
|
|
|
79
79
|
## Cursor
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
> **Current status: manual install.** `dbcli-agent` is not yet indexed in
|
|
82
|
+
> Cursor's plugin marketplace, so `/add-plugin dbcli-agent` in Cursor Agent chat
|
|
83
|
+
> will not find it. Use one of the manual fallbacks below. The `/add-plugin`
|
|
84
|
+
> command only works once the plugin has been indexed (see the submission steps
|
|
85
|
+
> that follow).
|
|
86
|
+
|
|
87
|
+
Once the plugin is indexed, Cursor can install it from Cursor Agent chat:
|
|
83
88
|
|
|
84
89
|
```text
|
|
85
90
|
/add-plugin dbcli-agent
|
|
@@ -93,18 +98,22 @@ This repository includes Cursor plugin metadata at:
|
|
|
93
98
|
.cursor-plugin/plugin.json
|
|
94
99
|
```
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
To get `/add-plugin` working, submit the repo for Cursor marketplace
|
|
102
|
+
review/indexing:
|
|
97
103
|
|
|
98
104
|
1. Push this repository to GitHub and keep it public.
|
|
99
105
|
2. Keep the single-plugin layout at the repository root:
|
|
100
106
|
`.cursor-plugin/plugin.json` plus `skills/dbcli/`.
|
|
101
|
-
3. Confirm the manifest name is lowercase kebab-case (`dbcli-agent`)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
3. Confirm the manifest name is lowercase kebab-case (`dbcli-agent`), the skill
|
|
108
|
+
files carry YAML frontmatter, `README.md` exists at the repo root, and all
|
|
109
|
+
manifest paths are relative with no `..` or absolute paths.
|
|
110
|
+
4. Submit the repository at <https://cursor.com/marketplace/publish>. This is the
|
|
111
|
+
current channel — the marketplace launched with Cursor 2.5 (2026-02-17) and
|
|
112
|
+
every plugin is manually reviewed and must be open source. Do **not** email
|
|
113
|
+
`kniparko@anysphere.com`; that address came from an older plugin template and
|
|
114
|
+
is not the submission path.
|
|
115
|
+
5. After the plugin is reviewed and listed, users can install from Cursor Agent
|
|
116
|
+
chat with `/add-plugin dbcli-agent` or by searching the Cursor marketplace.
|
|
108
117
|
|
|
109
118
|
The repo also keeps an instruction-file fallback for projects that want to
|
|
110
119
|
vendor the dbcli rule and reference directly.
|