@staff0rd/assist 0.207.1 → 0.209.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 +365 -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 and print rows affected (rejects non-mutating statements like pure SELECTs)
|
|
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 (INSERT, UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE, MERGE, GRANT, REVOKE, EXEC) and print rows affected. Rejects non-mutating statements (pure SELECTs, comment-only inputs)
|
|
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 and `mutate` for mutating statements — each command rejects the other kind, so do not work around the guards.
|
|
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.209.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,275 @@ 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
|
+
import chalk137 from "chalk";
|
|
11905
|
+
|
|
11906
|
+
// src/commands/sql/isMutation.ts
|
|
11907
|
+
var MUTATION_KEYWORDS = [
|
|
11908
|
+
"INSERT",
|
|
11909
|
+
"UPDATE",
|
|
11910
|
+
"DELETE",
|
|
11911
|
+
"DROP",
|
|
11912
|
+
"CREATE",
|
|
11913
|
+
"ALTER",
|
|
11914
|
+
"TRUNCATE",
|
|
11915
|
+
"MERGE",
|
|
11916
|
+
"GRANT",
|
|
11917
|
+
"REVOKE",
|
|
11918
|
+
"EXEC",
|
|
11919
|
+
"EXECUTE"
|
|
11920
|
+
];
|
|
11921
|
+
var MUTATION_PATTERN = new RegExp(
|
|
11922
|
+
`\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
|
|
11923
|
+
"i"
|
|
11924
|
+
);
|
|
11925
|
+
function stripComments(sql2) {
|
|
11926
|
+
return sql2.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
|
|
11927
|
+
}
|
|
11928
|
+
function isMutation(sql2) {
|
|
11929
|
+
const stripped = stripComments(sql2);
|
|
11930
|
+
if (MUTATION_PATTERN.test(stripped)) return true;
|
|
11931
|
+
return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
|
|
11932
|
+
}
|
|
11933
|
+
|
|
11934
|
+
// src/commands/sql/sqlMutate.ts
|
|
11935
|
+
async function sqlMutate(query, connectionName) {
|
|
11936
|
+
if (!isMutation(query)) {
|
|
11937
|
+
console.error(
|
|
11938
|
+
chalk137.red(
|
|
11939
|
+
"assist sql mutate refuses non-mutating statements. Use `assist sql query` instead."
|
|
11940
|
+
)
|
|
11941
|
+
);
|
|
11942
|
+
process.exit(1);
|
|
11943
|
+
}
|
|
11944
|
+
const conn = resolveConnection3(connectionName);
|
|
11945
|
+
const pool = await sqlConnect(conn);
|
|
11946
|
+
try {
|
|
11947
|
+
const result = await pool.request().query(query);
|
|
11948
|
+
console.log(chalk137.dim(`${result.rowsAffected.join(", ")} row(s) affected`));
|
|
11949
|
+
} finally {
|
|
11950
|
+
await pool.close();
|
|
11951
|
+
}
|
|
11952
|
+
}
|
|
11953
|
+
|
|
11954
|
+
// src/commands/sql/sqlQuery.ts
|
|
11955
|
+
import chalk138 from "chalk";
|
|
11956
|
+
async function sqlQuery(query, connectionName) {
|
|
11957
|
+
if (isMutation(query)) {
|
|
11958
|
+
console.error(
|
|
11959
|
+
chalk138.red(
|
|
11960
|
+
"assist sql query refuses mutating statements. Use `assist sql mutate` instead."
|
|
11961
|
+
)
|
|
11962
|
+
);
|
|
11963
|
+
process.exit(1);
|
|
11964
|
+
}
|
|
11965
|
+
const conn = resolveConnection3(connectionName);
|
|
11966
|
+
const pool = await sqlConnect(conn);
|
|
11967
|
+
try {
|
|
11968
|
+
const result = await pool.request().query(query);
|
|
11969
|
+
const rows = result.recordset ?? [];
|
|
11970
|
+
if (result.recordset) {
|
|
11971
|
+
printTable(rows);
|
|
11972
|
+
} else {
|
|
11973
|
+
console.log(
|
|
11974
|
+
chalk138.dim(`${result.rowsAffected.join(", ")} row(s) affected`)
|
|
11975
|
+
);
|
|
11976
|
+
}
|
|
11977
|
+
} finally {
|
|
11978
|
+
await pool.close();
|
|
11979
|
+
}
|
|
11980
|
+
}
|
|
11981
|
+
|
|
11982
|
+
// src/commands/sql/sqlSetConnection.ts
|
|
11983
|
+
function sqlSetConnection(name) {
|
|
11984
|
+
setNamedDefaultConnection(
|
|
11985
|
+
loadConnections3(),
|
|
11986
|
+
name,
|
|
11987
|
+
setDefaultConnection2,
|
|
11988
|
+
"SQL"
|
|
11989
|
+
);
|
|
11990
|
+
}
|
|
11991
|
+
|
|
11992
|
+
// src/commands/sql/sqlTables.ts
|
|
11993
|
+
async function sqlTables(connectionName) {
|
|
11994
|
+
const conn = resolveConnection3(connectionName);
|
|
11995
|
+
const pool = await sqlConnect(conn);
|
|
11996
|
+
try {
|
|
11997
|
+
const result = await pool.request().query(
|
|
11998
|
+
`SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE
|
|
11999
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
12000
|
+
ORDER BY TABLE_SCHEMA, TABLE_NAME`
|
|
12001
|
+
);
|
|
12002
|
+
const rows = result.recordset ?? [];
|
|
12003
|
+
printTable(rows);
|
|
12004
|
+
} finally {
|
|
12005
|
+
await pool.close();
|
|
12006
|
+
}
|
|
12007
|
+
}
|
|
12008
|
+
|
|
12009
|
+
// src/commands/registerSql.ts
|
|
12010
|
+
function registerSql(program2) {
|
|
12011
|
+
const cmd = program2.command("sql").description("MSSQL query utilities");
|
|
12012
|
+
const auth2 = cmd.command("auth").description("Configure a SQL connection");
|
|
12013
|
+
auth2.command("add").description("Add a new connection").action(() => sqlAuth.add());
|
|
12014
|
+
auth2.command("list").description("List configured connections").action(() => sqlAuth.list());
|
|
12015
|
+
auth2.command("remove <name>").description("Remove a configured connection").action((name) => sqlAuth.remove(name));
|
|
12016
|
+
cmd.command("set-connection <name>").description("Set the default SQL connection").action((name) => sqlSetConnection(name));
|
|
12017
|
+
cmd.command("query <sql> [connection]").description("Execute a read-only SQL query (rejects mutating statements)").action(
|
|
12018
|
+
(query, connection) => sqlQuery(query, connection)
|
|
12019
|
+
);
|
|
12020
|
+
cmd.command("mutate <sql> [connection]").description(
|
|
12021
|
+
"Execute a mutating SQL statement (rejects non-mutating statements)"
|
|
12022
|
+
).action(
|
|
12023
|
+
(query, connection) => sqlMutate(query, connection)
|
|
12024
|
+
);
|
|
12025
|
+
cmd.command("tables [connection]").description("List tables in the connected database").action((connection) => sqlTables(connection));
|
|
12026
|
+
cmd.command("columns <table> [connection]").description(
|
|
12027
|
+
"List columns for a table (use schema.table for non-default schema)"
|
|
12028
|
+
).action(
|
|
12029
|
+
(table, connection) => sqlColumns(table, connection)
|
|
12030
|
+
);
|
|
12031
|
+
}
|
|
12032
|
+
|
|
11723
12033
|
// src/commands/transcript/shared.ts
|
|
11724
12034
|
import { existsSync as existsSync32, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
11725
12035
|
import { basename as basename8, join as join34, relative as relative2 } from "path";
|
|
@@ -12241,14 +12551,14 @@ import {
|
|
|
12241
12551
|
import { dirname as dirname22, join as join38 } from "path";
|
|
12242
12552
|
|
|
12243
12553
|
// src/commands/transcript/summarise/processStagedFile/validateStagedContent.ts
|
|
12244
|
-
import
|
|
12554
|
+
import chalk139 from "chalk";
|
|
12245
12555
|
var FULL_TRANSCRIPT_REGEX = /^\[Full Transcript\]\(([^)]+)\)/;
|
|
12246
12556
|
function validateStagedContent(filename, content) {
|
|
12247
12557
|
const firstLine = content.split("\n")[0];
|
|
12248
12558
|
const match = firstLine.match(FULL_TRANSCRIPT_REGEX);
|
|
12249
12559
|
if (!match) {
|
|
12250
12560
|
console.error(
|
|
12251
|
-
|
|
12561
|
+
chalk139.red(
|
|
12252
12562
|
`Staged file ${filename} missing [Full Transcript](<path>) link on first line.`
|
|
12253
12563
|
)
|
|
12254
12564
|
);
|
|
@@ -12257,7 +12567,7 @@ function validateStagedContent(filename, content) {
|
|
|
12257
12567
|
const contentAfterLink = content.slice(firstLine.length).trim();
|
|
12258
12568
|
if (!contentAfterLink) {
|
|
12259
12569
|
console.error(
|
|
12260
|
-
|
|
12570
|
+
chalk139.red(
|
|
12261
12571
|
`Staged file ${filename} has no summary content after the transcript link.`
|
|
12262
12572
|
)
|
|
12263
12573
|
);
|
|
@@ -12653,7 +12963,7 @@ function registerVoice(program2) {
|
|
|
12653
12963
|
|
|
12654
12964
|
// src/commands/roam/auth.ts
|
|
12655
12965
|
import { randomBytes } from "crypto";
|
|
12656
|
-
import
|
|
12966
|
+
import chalk140 from "chalk";
|
|
12657
12967
|
|
|
12658
12968
|
// src/lib/openBrowser.ts
|
|
12659
12969
|
import { execSync as execSync37 } from "child_process";
|
|
@@ -12828,13 +13138,13 @@ async function auth() {
|
|
|
12828
13138
|
saveGlobalConfig(config);
|
|
12829
13139
|
const state = randomBytes(16).toString("hex");
|
|
12830
13140
|
console.log(
|
|
12831
|
-
|
|
13141
|
+
chalk140.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
|
|
12832
13142
|
);
|
|
12833
|
-
console.log(
|
|
12834
|
-
console.log(
|
|
12835
|
-
console.log(
|
|
13143
|
+
console.log(chalk140.white("http://localhost:14523/callback\n"));
|
|
13144
|
+
console.log(chalk140.blue("Opening browser for authorization..."));
|
|
13145
|
+
console.log(chalk140.dim("Waiting for authorization callback..."));
|
|
12836
13146
|
const { code, redirectUri } = await authorizeInBrowser(clientId, state);
|
|
12837
|
-
console.log(
|
|
13147
|
+
console.log(chalk140.dim("Exchanging code for tokens..."));
|
|
12838
13148
|
const tokens = await exchangeToken({
|
|
12839
13149
|
code,
|
|
12840
13150
|
clientId,
|
|
@@ -12850,7 +13160,7 @@ async function auth() {
|
|
|
12850
13160
|
};
|
|
12851
13161
|
saveGlobalConfig(config);
|
|
12852
13162
|
console.log(
|
|
12853
|
-
|
|
13163
|
+
chalk140.green("Roam credentials and tokens saved to ~/.assist.yml")
|
|
12854
13164
|
);
|
|
12855
13165
|
}
|
|
12856
13166
|
|
|
@@ -13263,7 +13573,7 @@ import { execSync as execSync39 } from "child_process";
|
|
|
13263
13573
|
import { existsSync as existsSync43, mkdirSync as mkdirSync16, unlinkSync as unlinkSync12, writeFileSync as writeFileSync30 } from "fs";
|
|
13264
13574
|
import { tmpdir as tmpdir7 } from "os";
|
|
13265
13575
|
import { join as join49, resolve as resolve13 } from "path";
|
|
13266
|
-
import
|
|
13576
|
+
import chalk141 from "chalk";
|
|
13267
13577
|
|
|
13268
13578
|
// src/commands/screenshot/captureWindowPs1.ts
|
|
13269
13579
|
var captureWindowPs1 = `
|
|
@@ -13414,20 +13724,20 @@ function screenshot(processName) {
|
|
|
13414
13724
|
const config = loadConfig();
|
|
13415
13725
|
const outputDir = resolve13(config.screenshot.outputDir);
|
|
13416
13726
|
const outputPath = buildOutputPath(outputDir, processName);
|
|
13417
|
-
console.log(
|
|
13727
|
+
console.log(chalk141.gray(`Capturing window for process "${processName}" ...`));
|
|
13418
13728
|
try {
|
|
13419
13729
|
runPowerShellScript(processName, outputPath);
|
|
13420
|
-
console.log(
|
|
13730
|
+
console.log(chalk141.green(`Screenshot saved: ${outputPath}`));
|
|
13421
13731
|
} catch (error) {
|
|
13422
13732
|
const msg = error instanceof Error ? error.message : String(error);
|
|
13423
|
-
console.error(
|
|
13733
|
+
console.error(chalk141.red(`Failed to capture screenshot: ${msg}`));
|
|
13424
13734
|
process.exit(1);
|
|
13425
13735
|
}
|
|
13426
13736
|
}
|
|
13427
13737
|
|
|
13428
13738
|
// src/commands/sessions/summarise/index.ts
|
|
13429
13739
|
import * as fs27 from "fs";
|
|
13430
|
-
import
|
|
13740
|
+
import chalk142 from "chalk";
|
|
13431
13741
|
|
|
13432
13742
|
// src/commands/sessions/summarise/shared.ts
|
|
13433
13743
|
import * as fs25 from "fs";
|
|
@@ -13567,22 +13877,22 @@ ${firstMessage}`);
|
|
|
13567
13877
|
async function summarise3(options2) {
|
|
13568
13878
|
const files = await discoverSessionJsonlPaths();
|
|
13569
13879
|
if (files.length === 0) {
|
|
13570
|
-
console.log(
|
|
13880
|
+
console.log(chalk142.yellow("No sessions found."));
|
|
13571
13881
|
return;
|
|
13572
13882
|
}
|
|
13573
13883
|
const toProcess = selectCandidates(files, options2);
|
|
13574
13884
|
if (toProcess.length === 0) {
|
|
13575
|
-
console.log(
|
|
13885
|
+
console.log(chalk142.green("All sessions already summarised."));
|
|
13576
13886
|
return;
|
|
13577
13887
|
}
|
|
13578
13888
|
console.log(
|
|
13579
|
-
|
|
13889
|
+
chalk142.cyan(
|
|
13580
13890
|
`Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
|
|
13581
13891
|
)
|
|
13582
13892
|
);
|
|
13583
13893
|
const { succeeded, failed } = processSessions(toProcess);
|
|
13584
13894
|
console.log(
|
|
13585
|
-
|
|
13895
|
+
chalk142.green(`Done: ${succeeded} summarised`) + (failed > 0 ? chalk142.yellow(`, ${failed} skipped`) : "")
|
|
13586
13896
|
);
|
|
13587
13897
|
}
|
|
13588
13898
|
function selectCandidates(files, options2) {
|
|
@@ -13602,16 +13912,16 @@ function processSessions(files) {
|
|
|
13602
13912
|
let failed = 0;
|
|
13603
13913
|
for (let i = 0; i < files.length; i++) {
|
|
13604
13914
|
const file = files[i];
|
|
13605
|
-
process.stdout.write(
|
|
13915
|
+
process.stdout.write(chalk142.dim(` [${i + 1}/${files.length}] `));
|
|
13606
13916
|
const summary = summariseSession(file);
|
|
13607
13917
|
if (summary) {
|
|
13608
13918
|
writeSummary(file, summary);
|
|
13609
13919
|
succeeded++;
|
|
13610
|
-
process.stdout.write(`${
|
|
13920
|
+
process.stdout.write(`${chalk142.green("\u2713")} ${summary}
|
|
13611
13921
|
`);
|
|
13612
13922
|
} else {
|
|
13613
13923
|
failed++;
|
|
13614
|
-
process.stdout.write(` ${
|
|
13924
|
+
process.stdout.write(` ${chalk142.yellow("skip")}
|
|
13615
13925
|
`);
|
|
13616
13926
|
}
|
|
13617
13927
|
}
|
|
@@ -13626,10 +13936,10 @@ function registerSessions(program2) {
|
|
|
13626
13936
|
}
|
|
13627
13937
|
|
|
13628
13938
|
// src/commands/statusLine.ts
|
|
13629
|
-
import
|
|
13939
|
+
import chalk144 from "chalk";
|
|
13630
13940
|
|
|
13631
13941
|
// src/commands/buildLimitsSegment.ts
|
|
13632
|
-
import
|
|
13942
|
+
import chalk143 from "chalk";
|
|
13633
13943
|
var FIVE_HOUR_SECONDS = 5 * 3600;
|
|
13634
13944
|
var SEVEN_DAY_SECONDS = 7 * 86400;
|
|
13635
13945
|
function formatTimeLeft(resetsAt) {
|
|
@@ -13652,10 +13962,10 @@ function projectUsage(pct, resetsAt, windowSeconds) {
|
|
|
13652
13962
|
function colorizeRateLimit(pct, resetsAt, windowSeconds) {
|
|
13653
13963
|
const label2 = `${Math.round(pct)}%`;
|
|
13654
13964
|
const projected = projectUsage(pct, resetsAt, windowSeconds);
|
|
13655
|
-
if (projected == null) return
|
|
13656
|
-
if (projected > 100) return
|
|
13657
|
-
if (projected > 75) return
|
|
13658
|
-
return
|
|
13965
|
+
if (projected == null) return chalk143.green(label2);
|
|
13966
|
+
if (projected > 100) return chalk143.red(label2);
|
|
13967
|
+
if (projected > 75) return chalk143.yellow(label2);
|
|
13968
|
+
return chalk143.green(label2);
|
|
13659
13969
|
}
|
|
13660
13970
|
function formatLimit(pct, resetsAt, windowSeconds, fallbackLabel) {
|
|
13661
13971
|
const timeLabel = resetsAt ? formatTimeLeft(resetsAt) : fallbackLabel;
|
|
@@ -13681,14 +13991,14 @@ function buildLimitsSegment(rateLimits) {
|
|
|
13681
13991
|
}
|
|
13682
13992
|
|
|
13683
13993
|
// src/commands/statusLine.ts
|
|
13684
|
-
|
|
13994
|
+
chalk144.level = 3;
|
|
13685
13995
|
function formatNumber(num) {
|
|
13686
13996
|
return num.toLocaleString("en-US");
|
|
13687
13997
|
}
|
|
13688
13998
|
function colorizePercent(pct) {
|
|
13689
13999
|
const label2 = `${Math.round(pct)}%`;
|
|
13690
|
-
if (pct > 80) return
|
|
13691
|
-
if (pct > 40) return
|
|
14000
|
+
if (pct > 80) return chalk144.red(label2);
|
|
14001
|
+
if (pct > 40) return chalk144.yellow(label2);
|
|
13692
14002
|
return label2;
|
|
13693
14003
|
}
|
|
13694
14004
|
async function statusLine() {
|
|
@@ -13711,7 +14021,7 @@ import { fileURLToPath as fileURLToPath7 } from "url";
|
|
|
13711
14021
|
// src/commands/sync/syncClaudeMd.ts
|
|
13712
14022
|
import * as fs28 from "fs";
|
|
13713
14023
|
import * as path49 from "path";
|
|
13714
|
-
import
|
|
14024
|
+
import chalk145 from "chalk";
|
|
13715
14025
|
async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
13716
14026
|
const source = path49.join(claudeDir, "CLAUDE.md");
|
|
13717
14027
|
const target = path49.join(targetBase, "CLAUDE.md");
|
|
@@ -13720,12 +14030,12 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
|
13720
14030
|
const targetContent = fs28.readFileSync(target, "utf-8");
|
|
13721
14031
|
if (sourceContent !== targetContent) {
|
|
13722
14032
|
console.log(
|
|
13723
|
-
|
|
14033
|
+
chalk145.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
|
|
13724
14034
|
);
|
|
13725
14035
|
console.log();
|
|
13726
14036
|
printDiff(targetContent, sourceContent);
|
|
13727
14037
|
const confirm = options2?.yes || await promptConfirm(
|
|
13728
|
-
|
|
14038
|
+
chalk145.red("Overwrite existing CLAUDE.md?"),
|
|
13729
14039
|
false
|
|
13730
14040
|
);
|
|
13731
14041
|
if (!confirm) {
|
|
@@ -13741,7 +14051,7 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
|
13741
14051
|
// src/commands/sync/syncSettings.ts
|
|
13742
14052
|
import * as fs29 from "fs";
|
|
13743
14053
|
import * as path50 from "path";
|
|
13744
|
-
import
|
|
14054
|
+
import chalk146 from "chalk";
|
|
13745
14055
|
async function syncSettings(claudeDir, targetBase, options2) {
|
|
13746
14056
|
const source = path50.join(claudeDir, "settings.json");
|
|
13747
14057
|
const target = path50.join(targetBase, "settings.json");
|
|
@@ -13757,14 +14067,14 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
13757
14067
|
if (mergedContent !== normalizedTarget) {
|
|
13758
14068
|
if (!options2?.yes) {
|
|
13759
14069
|
console.log(
|
|
13760
|
-
|
|
14070
|
+
chalk146.yellow(
|
|
13761
14071
|
"\n\u26A0\uFE0F Warning: settings.json differs from existing file"
|
|
13762
14072
|
)
|
|
13763
14073
|
);
|
|
13764
14074
|
console.log();
|
|
13765
14075
|
printDiff(targetContent, mergedContent);
|
|
13766
14076
|
const confirm = await promptConfirm(
|
|
13767
|
-
|
|
14077
|
+
chalk146.red("Overwrite existing settings.json?"),
|
|
13768
14078
|
false
|
|
13769
14079
|
);
|
|
13770
14080
|
if (!confirm) {
|
|
@@ -13878,6 +14188,7 @@ registerDotnet(program);
|
|
|
13878
14188
|
registerNews(program);
|
|
13879
14189
|
registerRavendb(program);
|
|
13880
14190
|
registerSeq(program);
|
|
14191
|
+
registerSql(program);
|
|
13881
14192
|
registerTranscript(program);
|
|
13882
14193
|
registerVoice(program);
|
|
13883
14194
|
registerSessions(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@staff0rd/assist",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.209.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",
|