@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 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
@@ -41,6 +41,10 @@ assist roam show-claude-code-icon
41
41
  assist screenshot
42
42
  assist seq auth list
43
43
  assist seq query
44
+ assist sql auth list
45
+ assist sql columns
46
+ assist sql query
47
+ assist sql tables
44
48
  assist status-line
45
49
  assist verify
46
50
  assist voice
@@ -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.
@@ -55,6 +55,7 @@
55
55
  "SlashCommand(/jira)",
56
56
  "Skill(seq)",
57
57
  "SlashCommand(/seq)",
58
+ "SlashCommand(/sql)",
58
59
  "Skill(inspect)",
59
60
  "SlashCommand(/inspect)",
60
61
  "Skill(prompts)",
@@ -41,6 +41,10 @@ assist roam show-claude-code-icon
41
41
  assist screenshot
42
42
  assist seq auth list
43
43
  assist seq query
44
+ assist sql auth list
45
+ assist sql columns
46
+ assist sql query
47
+ assist sql tables
44
48
  assist status-line
45
49
  assist verify
46
50
  assist voice
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.207.1",
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/commands/seq/promptConnection.ts
11470
+ // src/shared/assertUniqueName.ts
11456
11471
  import chalk127 from "chalk";
11457
- async function promptConnection2(existingNames) {
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 sql = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count}`;
11507
- const params = new URLSearchParams({ q: sql });
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/commands/seq/resolveConnection.ts
11661
+ // src/shared/resolveNamedConnection.ts
11642
11662
  import chalk131 from "chalk";
11643
- function resolveConnection2(name) {
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("No Seq connections configured. Run 'assist seq auth' first.")
11666
+ chalk131.red(
11667
+ `No ${kind} connections configured. Run '${authCommand}' first.`
11668
+ )
11648
11669
  );
11649
11670
  process.exit(1);
11650
11671
  }
11651
- const target = name ?? getDefaultConnection() ?? connections[0].name;
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(`Seq connection "${target}" not found.`));
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/commands/seq/seqSetConnection.ts
11726
+ // src/shared/setNamedDefaultConnection.ts
11695
11727
  import chalk133 from "chalk";
11696
- function seqSetConnection(name) {
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
- setDefaultConnection(name);
11703
- console.log(`Default Seq connection set to "${name}".`);
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 chalk134 from "chalk";
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
- chalk134.red(
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
- chalk134.red(
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 chalk135 from "chalk";
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
- chalk135.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
13141
+ chalk140.yellow("\nEnsure this Redirect URI is set in your Roam OAuth app:")
12832
13142
  );
12833
- console.log(chalk135.white("http://localhost:14523/callback\n"));
12834
- console.log(chalk135.blue("Opening browser for authorization..."));
12835
- console.log(chalk135.dim("Waiting for authorization callback..."));
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(chalk135.dim("Exchanging code for tokens..."));
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
- chalk135.green("Roam credentials and tokens saved to ~/.assist.yml")
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 chalk136 from "chalk";
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(chalk136.gray(`Capturing window for process "${processName}" ...`));
13727
+ console.log(chalk141.gray(`Capturing window for process "${processName}" ...`));
13418
13728
  try {
13419
13729
  runPowerShellScript(processName, outputPath);
13420
- console.log(chalk136.green(`Screenshot saved: ${outputPath}`));
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(chalk136.red(`Failed to capture screenshot: ${msg}`));
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 chalk137 from "chalk";
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(chalk137.yellow("No sessions found."));
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(chalk137.green("All sessions already summarised."));
13885
+ console.log(chalk142.green("All sessions already summarised."));
13576
13886
  return;
13577
13887
  }
13578
13888
  console.log(
13579
- chalk137.cyan(
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
- chalk137.green(`Done: ${succeeded} summarised`) + (failed > 0 ? chalk137.yellow(`, ${failed} skipped`) : "")
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(chalk137.dim(` [${i + 1}/${files.length}] `));
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(`${chalk137.green("\u2713")} ${summary}
13920
+ process.stdout.write(`${chalk142.green("\u2713")} ${summary}
13611
13921
  `);
13612
13922
  } else {
13613
13923
  failed++;
13614
- process.stdout.write(` ${chalk137.yellow("skip")}
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 chalk139 from "chalk";
13939
+ import chalk144 from "chalk";
13630
13940
 
13631
13941
  // src/commands/buildLimitsSegment.ts
13632
- import chalk138 from "chalk";
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 chalk138.green(label2);
13656
- if (projected > 100) return chalk138.red(label2);
13657
- if (projected > 75) return chalk138.yellow(label2);
13658
- return chalk138.green(label2);
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
- chalk139.level = 3;
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 chalk139.red(label2);
13691
- if (pct > 40) return chalk139.yellow(label2);
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 chalk140 from "chalk";
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
- chalk140.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
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
- chalk140.red("Overwrite existing CLAUDE.md?"),
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 chalk141 from "chalk";
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
- chalk141.yellow(
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
- chalk141.red("Overwrite existing settings.json?"),
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.207.1",
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",