@staff0rd/assist 0.207.1 → 0.208.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 +9 -0
- package/allowed.cli-reads +4 -0
- package/claude/commands/sql.md +32 -0
- package/claude/settings.json +1 -0
- package/dist/allowed.cli-reads +4 -0
- package/dist/index.js +347 -54
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ After installation, the `assist` command will be available globally. You can als
|
|
|
56
56
|
- `/screenshot` - Capture a screenshot of a running application window
|
|
57
57
|
- `/raven` - Query and manage RavenDB connections and collections
|
|
58
58
|
- `/seq` - Query Seq logs from a URL or filter expression
|
|
59
|
+
- `/sql` - Query a MSSQL database via assist sql
|
|
59
60
|
- `/verify` - Run all verification commands in parallel
|
|
60
61
|
- `/transcript-format` - Format meeting transcripts from VTT files
|
|
61
62
|
- `/transcript-summarise` - Summarise transcripts missing summaries
|
|
@@ -175,6 +176,14 @@ After installation, the `assist` command will be available globally. You can als
|
|
|
175
176
|
- `assist seq query <filter> -n <count>` - Fetch a specific number of events (default 50)
|
|
176
177
|
- `assist seq query <filter> --from <date>` - Start of query window (UTC date or relative e.g. 5m, 1h, 2d)
|
|
177
178
|
- `assist seq query <filter> --to <date>` - End of query window (UTC date or relative e.g. 5m, 1h, 2d)
|
|
179
|
+
- `assist sql auth add` - Add a new MSSQL connection (prompts for name, server, port, user, password, database)
|
|
180
|
+
- `assist sql auth list` - List configured SQL connections
|
|
181
|
+
- `assist sql auth remove <name>` - Remove a configured connection
|
|
182
|
+
- `assist sql set-connection <name>` - Set the default SQL connection
|
|
183
|
+
- `assist sql query "<sql>" [connection]` - Execute a read-only SQL statement and print results in table format (rejects INSERT/UPDATE/DELETE/DROP/CREATE/ALTER/TRUNCATE/MERGE/GRANT/REVOKE/EXEC)
|
|
184
|
+
- `assist sql mutate "<sql>" [connection]` - Execute a mutating SQL statement (not yet implemented)
|
|
185
|
+
- `assist sql tables [connection]` - List tables in the connected database (via INFORMATION_SCHEMA.TABLES)
|
|
186
|
+
- `assist sql columns <table> [connection]` - List columns for a table (use `schema.table` for non-default schema; via INFORMATION_SCHEMA.COLUMNS)
|
|
178
187
|
- `assist screenshot <process>` - Capture a screenshot of a running application window (e.g. `assist screenshot notepad`). Output directory is configurable via `screenshot.outputDir` (default `./screenshots`)
|
|
179
188
|
- `assist mermaid export [file.md]` - Render each fenced mermaid block to `<stem>-<index>.svg` via [Kroki](https://kroki.io). With no file, scans `*.md` in the current directory (non-recursive). Use `--out <dir>` to override the output directory. Use `--index <n>` to render only the nth mermaid block (1-based; requires a file argument). Endpoint is configurable via `mermaid.krokiUrl` (default `https://kroki.io`).
|
|
180
189
|
- `assist prompts` - Show top 10 denied tool calls by frequency with count and repo breakdown
|
package/allowed.cli-reads
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Query a MSSQL database via assist sql
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The user wants to query a MSSQL database. Use the `assist sql` CLI commands below.
|
|
6
|
+
|
|
7
|
+
## Connection management
|
|
8
|
+
|
|
9
|
+
- `assist sql auth add` — interactively add a named connection (name, server, port, user, password, database)
|
|
10
|
+
- `assist sql auth list` — list configured connections
|
|
11
|
+
- `assist sql auth remove <name>` — remove a connection
|
|
12
|
+
- `assist sql set-connection <name>` — set the default connection
|
|
13
|
+
|
|
14
|
+
## Schema introspection
|
|
15
|
+
|
|
16
|
+
- `assist sql tables [connection]` — list tables in the database
|
|
17
|
+
- `assist sql columns <table> [connection]` — list columns for a table (use `schema.table` for a non-default schema)
|
|
18
|
+
|
|
19
|
+
## Querying
|
|
20
|
+
|
|
21
|
+
- `assist sql query "<sql>" [connection]` — execute a read-only SQL statement and print results in table format. Rejects mutating statements (INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, MERGE, GRANT, REVOKE, EXEC)
|
|
22
|
+
- `assist sql mutate "<sql>" [connection]` — execute a mutating SQL statement (not yet implemented; will throw)
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
1. If no connections are configured, tell the user to run `assist sql auth add`. Do NOT attempt to add connections yourself.
|
|
27
|
+
2. If the user doesn't specify a connection, omit it to use the default.
|
|
28
|
+
3. Prefer `tables` and `columns` for schema discovery before crafting queries.
|
|
29
|
+
4. Quote SQL strings carefully — wrap the statement in double quotes and escape inner double quotes if needed.
|
|
30
|
+
5. Use `query` for SELECTs only. If the user asks for a mutation, tell them `assist sql mutate` is not yet implemented rather than working around the guard.
|
|
31
|
+
6. Display query results to the user. If the output is large, summarise key rows and highlight anything notable.
|
|
32
|
+
7. If the user asks follow-up questions, refine the query and re-run.
|
package/claude/settings.json
CHANGED
package/dist/allowed.cli-reads
CHANGED
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.208.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -48,6 +48,7 @@ var package_default = {
|
|
|
48
48
|
entities: "^7.0.1",
|
|
49
49
|
"is-wsl": "^3.1.0",
|
|
50
50
|
minimatch: "^10.1.1",
|
|
51
|
+
mssql: "^12.5.0",
|
|
51
52
|
"node-notifier": "^10.0.1",
|
|
52
53
|
"node-pty": "^1.1.0",
|
|
53
54
|
semver: "^7.7.3",
|
|
@@ -69,6 +70,7 @@ var package_default = {
|
|
|
69
70
|
"@semantic-release/git": "^10.0.1",
|
|
70
71
|
"@types/better-sqlite3": "^7.6.13",
|
|
71
72
|
"@types/blessed": "^0.1.27",
|
|
73
|
+
"@types/mssql": "^12.3.0",
|
|
72
74
|
"@types/node": "^24.10.1",
|
|
73
75
|
"@types/node-notifier": "^8.0.5",
|
|
74
76
|
"@types/react": "^19.2.14",
|
|
@@ -1787,6 +1789,19 @@ var assistConfigSchema = z3.strictObject({
|
|
|
1787
1789
|
).default([]),
|
|
1788
1790
|
defaultConnection: z3.string().optional()
|
|
1789
1791
|
}).optional(),
|
|
1792
|
+
sql: z3.strictObject({
|
|
1793
|
+
connections: z3.array(
|
|
1794
|
+
z3.strictObject({
|
|
1795
|
+
name: z3.string(),
|
|
1796
|
+
server: z3.string(),
|
|
1797
|
+
port: z3.number(),
|
|
1798
|
+
user: z3.string(),
|
|
1799
|
+
password: z3.string(),
|
|
1800
|
+
database: z3.string()
|
|
1801
|
+
})
|
|
1802
|
+
).default([]),
|
|
1803
|
+
defaultConnection: z3.string().optional()
|
|
1804
|
+
}).optional(),
|
|
1790
1805
|
screenshot: z3.strictObject({
|
|
1791
1806
|
outputDir: z3.string().default("./screenshots")
|
|
1792
1807
|
}).default({ outputDir: "./screenshots" }),
|
|
@@ -11452,14 +11467,19 @@ function setDefaultConnection(name) {
|
|
|
11452
11467
|
saveGlobalConfig(raw);
|
|
11453
11468
|
}
|
|
11454
11469
|
|
|
11455
|
-
// src/
|
|
11470
|
+
// src/shared/assertUniqueName.ts
|
|
11456
11471
|
import chalk127 from "chalk";
|
|
11457
|
-
|
|
11458
|
-
const name = await promptInput("name", "Connection name:", "default");
|
|
11472
|
+
function assertUniqueName(existingNames, name) {
|
|
11459
11473
|
if (existingNames.includes(name)) {
|
|
11460
11474
|
console.error(chalk127.red(`Connection "${name}" already exists.`));
|
|
11461
11475
|
process.exit(1);
|
|
11462
11476
|
}
|
|
11477
|
+
}
|
|
11478
|
+
|
|
11479
|
+
// src/commands/seq/promptConnection.ts
|
|
11480
|
+
async function promptConnection2(existingNames) {
|
|
11481
|
+
const name = await promptInput("name", "Connection name:", "default");
|
|
11482
|
+
assertUniqueName(existingNames, name);
|
|
11463
11483
|
const url = await promptInput("url", "Seq URL:", "http://localhost:5341");
|
|
11464
11484
|
const apiToken = await promptPassword("apiToken", "API token:");
|
|
11465
11485
|
return { name, url, apiToken };
|
|
@@ -11503,8 +11523,8 @@ function filterToSql(filter) {
|
|
|
11503
11523
|
// src/commands/seq/fetchSeqData.ts
|
|
11504
11524
|
async function fetchSeqData(conn, filter, count, from, to) {
|
|
11505
11525
|
const sqlFilter = filterToSql(filter);
|
|
11506
|
-
const
|
|
11507
|
-
const params = new URLSearchParams({ q:
|
|
11526
|
+
const sql2 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count}`;
|
|
11527
|
+
const params = new URLSearchParams({ q: sql2 });
|
|
11508
11528
|
if (from) params.set("fromDateUtc", from);
|
|
11509
11529
|
if (to) params.set("toDateUtc", to);
|
|
11510
11530
|
const response = await fetchSeq(conn, "/api/data", params);
|
|
@@ -11638,25 +11658,37 @@ function rejectTimestampFilter(filter) {
|
|
|
11638
11658
|
}
|
|
11639
11659
|
}
|
|
11640
11660
|
|
|
11641
|
-
// src/
|
|
11661
|
+
// src/shared/resolveNamedConnection.ts
|
|
11642
11662
|
import chalk131 from "chalk";
|
|
11643
|
-
function
|
|
11644
|
-
const connections = loadConnections2();
|
|
11663
|
+
function resolveNamedConnection(connections, requested, defaultName, kind, authCommand) {
|
|
11645
11664
|
if (connections.length === 0) {
|
|
11646
11665
|
console.error(
|
|
11647
|
-
chalk131.red(
|
|
11666
|
+
chalk131.red(
|
|
11667
|
+
`No ${kind} connections configured. Run '${authCommand}' first.`
|
|
11668
|
+
)
|
|
11648
11669
|
);
|
|
11649
11670
|
process.exit(1);
|
|
11650
11671
|
}
|
|
11651
|
-
const target =
|
|
11672
|
+
const target = requested ?? defaultName ?? connections[0].name;
|
|
11652
11673
|
const connection = connections.find((c) => c.name === target);
|
|
11653
11674
|
if (!connection) {
|
|
11654
|
-
console.error(chalk131.red(
|
|
11675
|
+
console.error(chalk131.red(`${kind} connection "${target}" not found.`));
|
|
11655
11676
|
process.exit(1);
|
|
11656
11677
|
}
|
|
11657
11678
|
return connection;
|
|
11658
11679
|
}
|
|
11659
11680
|
|
|
11681
|
+
// src/commands/seq/resolveConnection.ts
|
|
11682
|
+
function resolveConnection2(name) {
|
|
11683
|
+
return resolveNamedConnection(
|
|
11684
|
+
loadConnections2(),
|
|
11685
|
+
name,
|
|
11686
|
+
getDefaultConnection(),
|
|
11687
|
+
"Seq",
|
|
11688
|
+
"assist seq auth"
|
|
11689
|
+
);
|
|
11690
|
+
}
|
|
11691
|
+
|
|
11660
11692
|
// src/commands/seq/seqQuery.ts
|
|
11661
11693
|
async function seqQuery(filter, options2) {
|
|
11662
11694
|
rejectTimestampFilter(filter);
|
|
@@ -11691,16 +11723,25 @@ ${events.length} events`));
|
|
|
11691
11723
|
}
|
|
11692
11724
|
}
|
|
11693
11725
|
|
|
11694
|
-
// src/
|
|
11726
|
+
// src/shared/setNamedDefaultConnection.ts
|
|
11695
11727
|
import chalk133 from "chalk";
|
|
11696
|
-
function
|
|
11697
|
-
const connections = loadConnections2();
|
|
11728
|
+
function setNamedDefaultConnection(connections, name, setDefault, kind) {
|
|
11698
11729
|
if (!connections.find((c) => c.name === name)) {
|
|
11699
11730
|
console.error(chalk133.red(`Connection "${name}" not found.`));
|
|
11700
11731
|
process.exit(1);
|
|
11701
11732
|
}
|
|
11702
|
-
|
|
11703
|
-
console.log(`Default
|
|
11733
|
+
setDefault(name);
|
|
11734
|
+
console.log(`Default ${kind} connection set to "${name}".`);
|
|
11735
|
+
}
|
|
11736
|
+
|
|
11737
|
+
// src/commands/seq/seqSetConnection.ts
|
|
11738
|
+
function seqSetConnection(name) {
|
|
11739
|
+
setNamedDefaultConnection(
|
|
11740
|
+
loadConnections2(),
|
|
11741
|
+
name,
|
|
11742
|
+
setDefaultConnection,
|
|
11743
|
+
"Seq"
|
|
11744
|
+
);
|
|
11704
11745
|
}
|
|
11705
11746
|
|
|
11706
11747
|
// src/commands/registerSeq.ts
|
|
@@ -11720,6 +11761,257 @@ function registerSeq(program2) {
|
|
|
11720
11761
|
).option("--json", "Output raw JSON").action((filter, options2) => seqQuery(filter, options2));
|
|
11721
11762
|
}
|
|
11722
11763
|
|
|
11764
|
+
// src/commands/sql/sqlAuth.ts
|
|
11765
|
+
import chalk135 from "chalk";
|
|
11766
|
+
|
|
11767
|
+
// src/commands/sql/loadConnections.ts
|
|
11768
|
+
function loadConnections3() {
|
|
11769
|
+
const raw = loadGlobalConfigRaw();
|
|
11770
|
+
const sql2 = raw.sql;
|
|
11771
|
+
return sql2?.connections ?? [];
|
|
11772
|
+
}
|
|
11773
|
+
function saveConnections3(connections) {
|
|
11774
|
+
const raw = loadGlobalConfigRaw();
|
|
11775
|
+
const sql2 = raw.sql ?? {};
|
|
11776
|
+
sql2.connections = connections;
|
|
11777
|
+
raw.sql = sql2;
|
|
11778
|
+
saveGlobalConfig(raw);
|
|
11779
|
+
}
|
|
11780
|
+
function getDefaultConnection2() {
|
|
11781
|
+
const raw = loadGlobalConfigRaw();
|
|
11782
|
+
const sql2 = raw.sql;
|
|
11783
|
+
return sql2?.defaultConnection;
|
|
11784
|
+
}
|
|
11785
|
+
function setDefaultConnection2(name) {
|
|
11786
|
+
const raw = loadGlobalConfigRaw();
|
|
11787
|
+
const sql2 = raw.sql ?? {};
|
|
11788
|
+
sql2.defaultConnection = name;
|
|
11789
|
+
raw.sql = sql2;
|
|
11790
|
+
saveGlobalConfig(raw);
|
|
11791
|
+
}
|
|
11792
|
+
|
|
11793
|
+
// src/commands/sql/promptConnection.ts
|
|
11794
|
+
import chalk134 from "chalk";
|
|
11795
|
+
async function promptConnection3(existingNames) {
|
|
11796
|
+
const name = await promptInput("name", "Connection name:", "default");
|
|
11797
|
+
assertUniqueName(existingNames, name);
|
|
11798
|
+
const server = await promptInput("server", "Server:", "localhost");
|
|
11799
|
+
const portStr = await promptInput("port", "Port:", "1433");
|
|
11800
|
+
const port = Number.parseInt(portStr, 10);
|
|
11801
|
+
if (!Number.isFinite(port)) {
|
|
11802
|
+
console.error(chalk134.red(`Invalid port "${portStr}".`));
|
|
11803
|
+
process.exit(1);
|
|
11804
|
+
}
|
|
11805
|
+
const user = await promptInput("user", "User:");
|
|
11806
|
+
const password = await promptPassword("password", "Password:");
|
|
11807
|
+
const database = await promptInput("database", "Database:");
|
|
11808
|
+
return { name, server, port, user, password, database };
|
|
11809
|
+
}
|
|
11810
|
+
|
|
11811
|
+
// src/commands/sql/sqlAuth.ts
|
|
11812
|
+
var sqlAuth = createConnectionAuth({
|
|
11813
|
+
load: loadConnections3,
|
|
11814
|
+
save: saveConnections3,
|
|
11815
|
+
format: (c) => `${chalk135.bold(c.name)} ${c.server}:${c.port}/${c.database} (${c.user})`,
|
|
11816
|
+
promptNew: promptConnection3,
|
|
11817
|
+
onFirst: (c) => setDefaultConnection2(c.name)
|
|
11818
|
+
});
|
|
11819
|
+
|
|
11820
|
+
// src/commands/sql/printTable.ts
|
|
11821
|
+
import chalk136 from "chalk";
|
|
11822
|
+
function formatCell(value) {
|
|
11823
|
+
if (value === null || value === void 0) return "";
|
|
11824
|
+
if (value instanceof Date) return value.toISOString();
|
|
11825
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
11826
|
+
return String(value);
|
|
11827
|
+
}
|
|
11828
|
+
function printTable(rows) {
|
|
11829
|
+
if (rows.length === 0) {
|
|
11830
|
+
console.log(chalk136.yellow("(no rows)"));
|
|
11831
|
+
return;
|
|
11832
|
+
}
|
|
11833
|
+
const columns = Object.keys(rows[0]);
|
|
11834
|
+
const widths = columns.map(
|
|
11835
|
+
(col) => Math.max(col.length, ...rows.map((r) => formatCell(r[col]).length))
|
|
11836
|
+
);
|
|
11837
|
+
const header = columns.map((c, i) => c.padEnd(widths[i])).join(" ");
|
|
11838
|
+
console.log(chalk136.dim(header));
|
|
11839
|
+
console.log(chalk136.dim("-".repeat(header.length)));
|
|
11840
|
+
for (const row of rows) {
|
|
11841
|
+
const line = columns.map((c, i) => formatCell(row[c]).padEnd(widths[i])).join(" ");
|
|
11842
|
+
console.log(line);
|
|
11843
|
+
}
|
|
11844
|
+
console.log(chalk136.dim(`
|
|
11845
|
+
${rows.length} row${rows.length === 1 ? "" : "s"}`));
|
|
11846
|
+
}
|
|
11847
|
+
|
|
11848
|
+
// src/commands/sql/resolveConnection.ts
|
|
11849
|
+
function resolveConnection3(name) {
|
|
11850
|
+
return resolveNamedConnection(
|
|
11851
|
+
loadConnections3(),
|
|
11852
|
+
name,
|
|
11853
|
+
getDefaultConnection2(),
|
|
11854
|
+
"SQL",
|
|
11855
|
+
"assist sql auth add"
|
|
11856
|
+
);
|
|
11857
|
+
}
|
|
11858
|
+
|
|
11859
|
+
// src/commands/sql/sqlConnect.ts
|
|
11860
|
+
import sql from "mssql";
|
|
11861
|
+
async function sqlConnect(conn) {
|
|
11862
|
+
return await sql.connect({
|
|
11863
|
+
server: conn.server,
|
|
11864
|
+
port: conn.port,
|
|
11865
|
+
user: conn.user,
|
|
11866
|
+
password: conn.password,
|
|
11867
|
+
database: conn.database,
|
|
11868
|
+
options: {
|
|
11869
|
+
encrypt: false,
|
|
11870
|
+
trustServerCertificate: true
|
|
11871
|
+
}
|
|
11872
|
+
});
|
|
11873
|
+
}
|
|
11874
|
+
|
|
11875
|
+
// src/commands/sql/sqlColumns.ts
|
|
11876
|
+
async function sqlColumns(table, connectionName) {
|
|
11877
|
+
const conn = resolveConnection3(connectionName);
|
|
11878
|
+
const pool = await sqlConnect(conn);
|
|
11879
|
+
try {
|
|
11880
|
+
const parts = table.split(".");
|
|
11881
|
+
const schema = parts.length > 1 ? parts[0] : void 0;
|
|
11882
|
+
const name = parts.length > 1 ? parts[1] : parts[0];
|
|
11883
|
+
const request = pool.request().input("table", name);
|
|
11884
|
+
let where = "TABLE_NAME = @table";
|
|
11885
|
+
if (schema) {
|
|
11886
|
+
request.input("schema", schema);
|
|
11887
|
+
where += " AND TABLE_SCHEMA = @schema";
|
|
11888
|
+
}
|
|
11889
|
+
const result = await request.query(
|
|
11890
|
+
`SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION,
|
|
11891
|
+
DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, IS_NULLABLE, COLUMN_DEFAULT
|
|
11892
|
+
FROM INFORMATION_SCHEMA.COLUMNS
|
|
11893
|
+
WHERE ${where}
|
|
11894
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION`
|
|
11895
|
+
);
|
|
11896
|
+
const rows = result.recordset ?? [];
|
|
11897
|
+
printTable(rows);
|
|
11898
|
+
} finally {
|
|
11899
|
+
await pool.close();
|
|
11900
|
+
}
|
|
11901
|
+
}
|
|
11902
|
+
|
|
11903
|
+
// src/commands/sql/sqlMutate.ts
|
|
11904
|
+
async function sqlMutate(_query, _connectionName) {
|
|
11905
|
+
throw new Error("assist sql mutate is not yet implemented");
|
|
11906
|
+
}
|
|
11907
|
+
|
|
11908
|
+
// src/commands/sql/sqlQuery.ts
|
|
11909
|
+
import chalk137 from "chalk";
|
|
11910
|
+
|
|
11911
|
+
// src/commands/sql/isMutation.ts
|
|
11912
|
+
var MUTATION_KEYWORDS = [
|
|
11913
|
+
"INSERT",
|
|
11914
|
+
"UPDATE",
|
|
11915
|
+
"DELETE",
|
|
11916
|
+
"DROP",
|
|
11917
|
+
"CREATE",
|
|
11918
|
+
"ALTER",
|
|
11919
|
+
"TRUNCATE",
|
|
11920
|
+
"MERGE",
|
|
11921
|
+
"GRANT",
|
|
11922
|
+
"REVOKE",
|
|
11923
|
+
"EXEC",
|
|
11924
|
+
"EXECUTE"
|
|
11925
|
+
];
|
|
11926
|
+
var MUTATION_PATTERN = new RegExp(
|
|
11927
|
+
`\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
|
|
11928
|
+
"i"
|
|
11929
|
+
);
|
|
11930
|
+
function stripComments(sql2) {
|
|
11931
|
+
return sql2.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
|
|
11932
|
+
}
|
|
11933
|
+
function isMutation(sql2) {
|
|
11934
|
+
const stripped = stripComments(sql2);
|
|
11935
|
+
if (MUTATION_PATTERN.test(stripped)) return true;
|
|
11936
|
+
return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
|
|
11937
|
+
}
|
|
11938
|
+
|
|
11939
|
+
// src/commands/sql/sqlQuery.ts
|
|
11940
|
+
async function sqlQuery(query, connectionName) {
|
|
11941
|
+
if (isMutation(query)) {
|
|
11942
|
+
console.error(
|
|
11943
|
+
chalk137.red(
|
|
11944
|
+
"assist sql query refuses mutating statements. Use `assist sql mutate` instead."
|
|
11945
|
+
)
|
|
11946
|
+
);
|
|
11947
|
+
process.exit(1);
|
|
11948
|
+
}
|
|
11949
|
+
const conn = resolveConnection3(connectionName);
|
|
11950
|
+
const pool = await sqlConnect(conn);
|
|
11951
|
+
try {
|
|
11952
|
+
const result = await pool.request().query(query);
|
|
11953
|
+
const rows = result.recordset ?? [];
|
|
11954
|
+
if (result.recordset) {
|
|
11955
|
+
printTable(rows);
|
|
11956
|
+
} else {
|
|
11957
|
+
console.log(
|
|
11958
|
+
chalk137.dim(`${result.rowsAffected.join(", ")} row(s) affected`)
|
|
11959
|
+
);
|
|
11960
|
+
}
|
|
11961
|
+
} finally {
|
|
11962
|
+
await pool.close();
|
|
11963
|
+
}
|
|
11964
|
+
}
|
|
11965
|
+
|
|
11966
|
+
// src/commands/sql/sqlSetConnection.ts
|
|
11967
|
+
function sqlSetConnection(name) {
|
|
11968
|
+
setNamedDefaultConnection(
|
|
11969
|
+
loadConnections3(),
|
|
11970
|
+
name,
|
|
11971
|
+
setDefaultConnection2,
|
|
11972
|
+
"SQL"
|
|
11973
|
+
);
|
|
11974
|
+
}
|
|
11975
|
+
|
|
11976
|
+
// src/commands/sql/sqlTables.ts
|
|
11977
|
+
async function sqlTables(connectionName) {
|
|
11978
|
+
const conn = resolveConnection3(connectionName);
|
|
11979
|
+
const pool = await sqlConnect(conn);
|
|
11980
|
+
try {
|
|
11981
|
+
const result = await pool.request().query(
|
|
11982
|
+
`SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE
|
|
11983
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
11984
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME`
|
|
11985
|
+
);
|
|
11986
|
+
const rows = result.recordset ?? [];
|
|
11987
|
+
printTable(rows);
|
|
11988
|
+
} finally {
|
|
11989
|
+
await pool.close();
|
|
11990
|
+
}
|
|
11991
|
+
}
|
|
11992
|
+
|
|
11993
|
+
// src/commands/registerSql.ts
|
|
11994
|
+
function registerSql(program2) {
|
|
11995
|
+
const cmd = program2.command("sql").description("MSSQL query utilities");
|
|
11996
|
+
const auth2 = cmd.command("auth").description("Configure a SQL connection");
|
|
11997
|
+
auth2.command("add").description("Add a new connection").action(() => sqlAuth.add());
|
|
11998
|
+
auth2.command("list").description("List configured connections").action(() => sqlAuth.list());
|
|
11999
|
+
auth2.command("remove <name>").description("Remove a configured connection").action((name) => sqlAuth.remove(name));
|
|
12000
|
+
cmd.command("set-connection <name>").description("Set the default SQL connection").action((name) => sqlSetConnection(name));
|
|
12001
|
+
cmd.command("query <sql> [connection]").description("Execute a read-only SQL query (rejects mutating statements)").action(
|
|
12002
|
+
(query, connection) => sqlQuery(query, connection)
|
|
12003
|
+
);
|
|
12004
|
+
cmd.command("mutate <sql> [connection]").description("Execute a mutating SQL statement (not yet implemented)").action(
|
|
12005
|
+
(query, connection) => sqlMutate(query, connection)
|
|
12006
|
+
);
|
|
12007
|
+
cmd.command("tables [connection]").description("List tables in the connected database").action((connection) => sqlTables(connection));
|
|
12008
|
+
cmd.command("columns <table> [connection]").description(
|
|
12009
|
+
"List columns for a table (use schema.table for non-default schema)"
|
|
12010
|
+
).action(
|
|
12011
|
+
(table, connection) => sqlColumns(table, connection)
|
|
12012
|
+
);
|
|
12013
|
+
}
|
|
12014
|
+
|
|
11723
12015
|
// src/commands/transcript/shared.ts
|
|
11724
12016
|
import { existsSync as existsSync32, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
11725
12017
|
import { basename as basename8, join as join34, relative as relative2 } from "path";
|
|
@@ -12241,14 +12533,14 @@ import {
|
|
|
12241
12533
|
import { dirname as dirname22, join as join38 } from "path";
|
|
12242
12534
|
|
|
12243
12535
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
12244
|
-
import
|
|
12536
|
+
import chalk138 from "chalk";
|
|
12245
12537
|
var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
|
|
12246
12538
|
function validateStagedContent(filename, content) {
|
|
12247
12539
|
const firstLine = content.split("\n")[0];
|
|
12248
12540
|
const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
|
|
12249
12541
|
if (!match) {
|
|
12250
12542
|
console.error(
|
|
12251
|
-
|
|
12543
|
+
chalk138.red(
|
|
12252
12544
|
`Staged file ${filename} missing [Full Transcript](<path>) link on first line.`
|
|
12253
12545
|
)
|
|
12254
12546
|
);
|
|
@@ -12257,7 +12549,7 @@ function validateStagedContent(filename, content) {
|
|
|
12257
12549
|
const contentAfterLink = content.slice(firstLine.length).trim();
|
|
12258
12550
|
if (!contentAfterLink) {
|
|
12259
12551
|
console.error(
|
|
12260
|
-
|
|
12552
|
+
chalk138.red(
|
|
12261
12553
|
`Staged file ${filename} has no summary content after the transcript link.`
|
|
12262
12554
|
)
|
|
12263
12555
|
);
|
|
@@ -12653,7 +12945,7 @@ function registerVoice(program2) {
|
|
|
12653
12945
|
|
|
12654
12946
|
// src/commands/roam/auth.ts
|
|
12655
12947
|
import { randomBytes } from "crypto";
|
|
12656
|
-
import
|
|
12948
|
+
import chalk139 from "chalk";
|
|
12657
12949
|
|
|
12658
12950
|
// src/lib/openBrowser.ts
|
|
12659
12951
|
import { execSync as execSync37 } from "child_process";
|
|
@@ -12828,13 +13120,13 @@ async function auth() {
|
|
|
12828
13120
|
saveGlobalConfig(config);
|
|
12829
13121
|
const state = randomBytes(16).toString("hex");
|
|
12830
13122
|
console.log(
|
|
12831
|
-
|
|
13123
|
+
chalk139.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
|
|
12832
13124
|
);
|
|
12833
|
-
console.log(
|
|
12834
|
-
console.log(
|
|
12835
|
-
console.log(
|
|
13125
|
+
console.log(chalk139.white("http://localhost:14523/callback\n"));
|
|
13126
|
+
console.log(chalk139.blue("Opening browser for authorization..."));
|
|
13127
|
+
console.log(chalk139.dim("Waiting for authorization callback..."));
|
|
12836
13128
|
const { code, redirectUri } = await authorizeInBrowser(clientId, state);
|
|
12837
|
-
console.log(
|
|
13129
|
+
console.log(chalk139.dim("Exchanging code for tokens..."));
|
|
12838
13130
|
const tokens = await exchangeToken({
|
|
12839
13131
|
code,
|
|
12840
13132
|
clientId,
|
|
@@ -12850,7 +13142,7 @@ async function auth() {
|
|
|
12850
13142
|
};
|
|
12851
13143
|
saveGlobalConfig(config);
|
|
12852
13144
|
console.log(
|
|
12853
|
-
|
|
13145
|
+
chalk139.green("Roam credentials and tokens saved to ~/.assist.yml")
|
|
12854
13146
|
);
|
|
12855
13147
|
}
|
|
12856
13148
|
|
|
@@ -13263,7 +13555,7 @@ import { execSync as execSync39 } from "child_process";
|
|
|
13263
13555
|
import { existsSync as existsSync43, mkdirSync as mkdirSync16, unlinkSync as unlinkSync12, writeFileSync as writeFileSync30 } from "fs";
|
|
13264
13556
|
import { tmpdir as tmpdir7 } from "os";
|
|
13265
13557
|
import { join as join49, resolve as resolve13 } from "path";
|
|
13266
|
-
import
|
|
13558
|
+
import chalk140 from "chalk";
|
|
13267
13559
|
|
|
13268
13560
|
// src/commands/screenshot/captureWindowPs1.ts
|
|
13269
13561
|
var captureWindowPs1 = `
|
|
@@ -13414,20 +13706,20 @@ function screenshot(processName) {
|
|
|
13414
13706
|
const config = loadConfig();
|
|
13415
13707
|
const outputDir = resolve13(config.screenshot.outputDir);
|
|
13416
13708
|
const outputPath = buildOutputPath(outputDir, processName);
|
|
13417
|
-
console.log(
|
|
13709
|
+
console.log(chalk140.gray(`Capturing window for process "${processName}" ...`));
|
|
13418
13710
|
try {
|
|
13419
13711
|
runPowerShellScript(processName, outputPath);
|
|
13420
|
-
console.log(
|
|
13712
|
+
console.log(chalk140.green(`Screenshot saved: ${outputPath}`));
|
|
13421
13713
|
} catch (error) {
|
|
13422
13714
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13423
|
-
console.error(
|
|
13715
|
+
console.error(chalk140.red(`Failed to capture screenshot: ${msg}`));
|
|
13424
13716
|
process.exit(1);
|
|
13425
13717
|
}
|
|
13426
13718
|
}
|
|
13427
13719
|
|
|
13428
13720
|
// src/commands/sessions/summarise/index.ts
|
|
13429
13721
|
import * as fs27 from "fs";
|
|
13430
|
-
import
|
|
13722
|
+
import chalk141 from "chalk";
|
|
13431
13723
|
|
|
13432
13724
|
// src/commands/sessions/summarise/shared.ts
|
|
13433
13725
|
import * as fs25 from "fs";
|
|
@@ -13567,22 +13859,22 @@ ${firstMessage}`);
|
|
|
13567
13859
|
async function summarise3(options2) {
|
|
13568
13860
|
const files = await discoverSessionJsonlPaths();
|
|
13569
13861
|
if (files.length === 0) {
|
|
13570
|
-
console.log(
|
|
13862
|
+
console.log(chalk141.yellow("No sessions found."));
|
|
13571
13863
|
return;
|
|
13572
13864
|
}
|
|
13573
13865
|
const toProcess = selectCandidates(files, options2);
|
|
13574
13866
|
if (toProcess.length === 0) {
|
|
13575
|
-
console.log(
|
|
13867
|
+
console.log(chalk141.green("All sessions already summarised."));
|
|
13576
13868
|
return;
|
|
13577
13869
|
}
|
|
13578
13870
|
console.log(
|
|
13579
|
-
|
|
13871
|
+
chalk141.cyan(
|
|
13580
13872
|
`Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
|
|
13581
13873
|
)
|
|
13582
13874
|
);
|
|
13583
13875
|
const { succeeded, failed } = processSessions(toProcess);
|
|
13584
13876
|
console.log(
|
|
13585
|
-
|
|
13877
|
+
chalk141.green(`Done: ${succeeded} summarised`) + (failed > 0 ? chalk141.yellow(`, ${failed} skipped`) : "")
|
|
13586
13878
|
);
|
|
13587
13879
|
}
|
|
13588
13880
|
function selectCandidates(files, options2) {
|
|
@@ -13602,16 +13894,16 @@ function processSessions(files) {
|
|
|
13602
13894
|
let failed = 0;
|
|
13603
13895
|
for (let i = 0; i < files.length; i++) {
|
|
13604
13896
|
const file = files[i];
|
|
13605
|
-
process.stdout.write(
|
|
13897
|
+
process.stdout.write(chalk141.dim(` [${i + 1}/${files.length}] `));
|
|
13606
13898
|
const summary = summariseSession(file);
|
|
13607
13899
|
if (summary) {
|
|
13608
13900
|
writeSummary(file, summary);
|
|
13609
13901
|
succeeded++;
|
|
13610
|
-
process.stdout.write(`${
|
|
13902
|
+
process.stdout.write(`${chalk141.green("\u2713")} ${summary}
|
|
13611
13903
|
`);
|
|
13612
13904
|
} else {
|
|
13613
13905
|
failed++;
|
|
13614
|
-
process.stdout.write(` ${
|
|
13906
|
+
process.stdout.write(` ${chalk141.yellow("skip")}
|
|
13615
13907
|
`);
|
|
13616
13908
|
}
|
|
13617
13909
|
}
|
|
@@ -13626,10 +13918,10 @@ function registerSessions(program2) {
|
|
|
13626
13918
|
}
|
|
13627
13919
|
|
|
13628
13920
|
// src/commands/statusLine.ts
|
|
13629
|
-
import
|
|
13921
|
+
import chalk143 from "chalk";
|
|
13630
13922
|
|
|
13631
13923
|
// src/commands/buildLimitsSegment.ts
|
|
13632
|
-
import
|
|
13924
|
+
import chalk142 from "chalk";
|
|
13633
13925
|
var FIVE_HOUR_SECONDS = 5 * 3600;
|
|
13634
13926
|
var SEVEN_DAY_SECONDS = 7 * 86400;
|
|
13635
13927
|
function formatTimeLeft(resetsAt) {
|
|
@@ -13652,10 +13944,10 @@ function projectUsage(pct, resetsAt, windowSeconds) {
|
|
|
13652
13944
|
function colorizeRateLimit(pct, resetsAt, windowSeconds) {
|
|
13653
13945
|
const label2 = `${Math.round(pct)}%`;
|
|
13654
13946
|
const projected = projectUsage(pct, resetsAt, windowSeconds);
|
|
13655
|
-
if (projected == null) return
|
|
13656
|
-
if (projected > 100) return
|
|
13657
|
-
if (projected > 75) return
|
|
13658
|
-
return
|
|
13947
|
+
if (projected == null) return chalk142.green(label2);
|
|
13948
|
+
if (projected > 100) return chalk142.red(label2);
|
|
13949
|
+
if (projected > 75) return chalk142.yellow(label2);
|
|
13950
|
+
return chalk142.green(label2);
|
|
13659
13951
|
}
|
|
13660
13952
|
function formatLimit(pct, resetsAt, windowSeconds, fallbackLabel) {
|
|
13661
13953
|
const timeLabel = resetsAt ? formatTimeLeft(resetsAt) : fallbackLabel;
|
|
@@ -13681,14 +13973,14 @@ function buildLimitsSegment(rateLimits) {
|
|
|
13681
13973
|
}
|
|
13682
13974
|
|
|
13683
13975
|
// src/commands/statusLine.ts
|
|
13684
|
-
|
|
13976
|
+
chalk143.level = 3;
|
|
13685
13977
|
function formatNumber(num) {
|
|
13686
13978
|
return num.toLocaleString("en-US");
|
|
13687
13979
|
}
|
|
13688
13980
|
function colorizePercent(pct) {
|
|
13689
13981
|
const label2 = `${Math.round(pct)}%`;
|
|
13690
|
-
if (pct > 80) return
|
|
13691
|
-
if (pct > 40) return
|
|
13982
|
+
if (pct > 80) return chalk143.red(label2);
|
|
13983
|
+
if (pct > 40) return chalk143.yellow(label2);
|
|
13692
13984
|
return label2;
|
|
13693
13985
|
}
|
|
13694
13986
|
async function statusLine() {
|
|
@@ -13711,7 +14003,7 @@ import { fileURLToPath as fileURLToPath7 } from "url";
|
|
|
13711
14003
|
// src/commands/sync/syncClaudeMd.ts
|
|
13712
14004
|
import * as fs28 from "fs";
|
|
13713
14005
|
import * as path49 from "path";
|
|
13714
|
-
import
|
|
14006
|
+
import chalk144 from "chalk";
|
|
13715
14007
|
async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
13716
14008
|
const source = path49.join(claudeDir, "CLAUDE.md");
|
|
13717
14009
|
const target = path49.join(targetBase, "CLAUDE.md");
|
|
@@ -13720,12 +14012,12 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
|
13720
14012
|
const targetContent = fs28.readFileSync(target, "utf-8");
|
|
13721
14013
|
if (sourceContent !== targetContent) {
|
|
13722
14014
|
console.log(
|
|
13723
|
-
|
|
14015
|
+
chalk144.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
|
|
13724
14016
|
);
|
|
13725
14017
|
console.log();
|
|
13726
14018
|
printDiff(targetContent, sourceContent);
|
|
13727
14019
|
const confirm = options2?.yes || await promptConfirm(
|
|
13728
|
-
|
|
14020
|
+
chalk144.red("Overwrite existing CLAUDE.md?"),
|
|
13729
14021
|
false
|
|
13730
14022
|
);
|
|
13731
14023
|
if (!confirm) {
|
|
@@ -13741,7 +14033,7 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
|
13741
14033
|
// src/commands/sync/syncSettings.ts
|
|
13742
14034
|
import * as fs29 from "fs";
|
|
13743
14035
|
import * as path50 from "path";
|
|
13744
|
-
import
|
|
14036
|
+
import chalk145 from "chalk";
|
|
13745
14037
|
async function syncSettings(claudeDir, targetBase, options2) {
|
|
13746
14038
|
const source = path50.join(claudeDir, "settings.json");
|
|
13747
14039
|
const target = path50.join(targetBase, "settings.json");
|
|
@@ -13757,14 +14049,14 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
13757
14049
|
if (mergedContent !== normalizedTarget) {
|
|
13758
14050
|
if (!options2?.yes) {
|
|
13759
14051
|
console.log(
|
|
13760
|
-
|
|
14052
|
+
chalk145.yellow(
|
|
13761
14053
|
"\n\u26A0\uFE0F Warning: settings.json differs from existing file"
|
|
13762
14054
|
)
|
|
13763
14055
|
);
|
|
13764
14056
|
console.log();
|
|
13765
14057
|
printDiff(targetContent, mergedContent);
|
|
13766
14058
|
const confirm = await promptConfirm(
|
|
13767
|
-
|
|
14059
|
+
chalk145.red("Overwrite existing settings.json?"),
|
|
13768
14060
|
false
|
|
13769
14061
|
);
|
|
13770
14062
|
if (!confirm) {
|
|
@@ -13878,6 +14170,7 @@ registerDotnet(program);
|
|
|
13878
14170
|
registerNews(program);
|
|
13879
14171
|
registerRavendb(program);
|
|
13880
14172
|
registerSeq(program);
|
|
14173
|
+
registerSql(program);
|
|
13881
14174
|
registerTranscript(program);
|
|
13882
14175
|
registerVoice(program);
|
|
13883
14176
|
registerSessions(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@staff0rd/assist",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.208.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"entities": "^7.0.1",
|
|
43
43
|
"is-wsl": "^3.1.0",
|
|
44
44
|
"minimatch": "^10.1.1",
|
|
45
|
+
"mssql": "^12.5.0",
|
|
45
46
|
"node-notifier": "^10.0.1",
|
|
46
47
|
"node-pty": "^1.1.0",
|
|
47
48
|
"semver": "^7.7.3",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"@semantic-release/git": "^10.0.1",
|
|
64
65
|
"@types/better-sqlite3": "^7.6.13",
|
|
65
66
|
"@types/blessed": "^0.1.27",
|
|
67
|
+
"@types/mssql": "^12.3.0",
|
|
66
68
|
"@types/node": "^24.10.1",
|
|
67
69
|
"@types/node-notifier": "^8.0.5",
|
|
68
70
|
"@types/react": "^19.2.14",
|